ActiveResource for Yii

Hi folks,

I just pushed an alpha version of "ActiveResource for Yii" to Github (https://github.com/Haensel/ActiveResource)

What is Active Resource?

The goal is to use REST services and their resources as if they were schemaless ActiveRecord models. I was using a lot of different REST services these days (Neo4j for example is a graph database exposed as RESTful service) and soon recognized that a lot of them could need the same features an CActiveRecord model would provide.

So instead of using a database as persistent storage this class uses a REST service. This project is inspired by the Rails version of ActiveResource (http://api.rubyonrails.org/classes/ActiveResource/Base.html)

How do you I use it?

Please follow the instructions on GitHub. It should be really straight forward.

Give me an example so I can see if this could be interesting for me:

Imagine a webservice exposing "people" resources via REST. A simple EActiveResource model could look like this:


<?php

class Person extends EActiveResource

{


    public static function model($className=__CLASS__)

    {

        return parent::model($className);

    }


    public function rest()

    {

        return array(

            'site'=>'http://api.aRESTservice.com',

            'resource'=>'people',

            'contenttype'=>'application/json',

            'accepttype'=>'application/json',

            'fileextension'=>'.json', //Twitter always wants you to append this to your GET requests for example

        );

    }

    

}

?>

Then you want to add a new Person




$person=new Person;

$person->setAttributes(array(

	'name'=>'Haensel',

	'gender'=>'m'

));

$person->save(); //validation fails, no POST request is sent to the service. You can get the error messages like you would with CActiveRecord


$person=new Person;

$person->setAttributes(array(

	'name'=>'Haensel',

	'gender'=>1

));

$person->save(); // VALIDATED. Sending POST request to http://api.aRESTservice.com/people with data '{'name':'Haensel','gender':1}'



Ok, so why isn’t this an extension already?

This is an early version and I don’t want you to be mad at me when it sucks. For example basic authentication isn’t fully implemented yet although that should be easy. OAuth for example could be a bit more tricky. And it isn’t possible to send XML, you are only able to RECEIVE XML and JSON or send JSON or urlencoded data.

If the release becomes stable I’ll sure add it to the extensions.

So what do you think? Nice, bad? Anything is welcome and if you find bugs or general problems you can post it to my GitHub repository. This should be the HQ. If you have any additional question drop me a line on twitter (@Haensel).

I also used extensive tracing, so activate tracing and look for "ext.EActiveResource" to find possible problems.

Cheers,

Haensel

Even though i didn’t have to deal with REST yet, this seems to be pretty useful. I also like the simple syntax that resembles AR. Maybe i would not call the configuration method “Configuration” which doesn’t look very Yii’ish :) How about “rest()” or something, similar to AR’s table() method?

Apart from that i wonder, if you can also extend it with find*() operations. But this probably requires a standard query schema for REST - which probably doesn’t exist, right?

Hi Mike,

You are right, maybe I’ll rename this :)

It should be easily extensible, but as there is no “fits all” way of doing it I desided to just implement the very basic AR functions (like findById(), updateById(),deleteById()). In case of find() some services would require a GET request including parameters like “?name=Test&gender=1”, some would need a prefix like “q” “?q=name:test&gender:1”, others even need a POST request with some complex query in the body of the request (although that’s not a very RESTful approach).

Just to give you an example:

Twitter exposes statuses and users etc. via REST (although not very RESTful, meaning no standard schema is used), so one would have to play around with it a bit. I want statuses and statuses always return an embedded user object, so one could write two classes

First, let’s specify a Tweet model


<?php


class Tweet extends EActiveResource

{


    public static function model($className=__CLASS__)

    {

        return parent::model($className);

    }


    public function rest()

    {

        return array(

            'site'=>'http://api.twitter.com/1',

            'resource'=>'statuses',

            'contenttype'=>'application/json',

            'accepttype'=>'application/xml',

            'container'=>'status',

            'fileextension'=>'.xml',

            'embedded'=>array(

                'user'=>array(self::IS_ONE,'TwitterUser')

            ),


        );

    }


    public function findById($id)

    {

        return $this->populateRecord($this->getRequest('show/'.$id));

    }


    public function publicTimeline()

    {

        return self::model()->populateRecords(self::model()->getRequest('public_timeline'));

    }

    

}


?>



A tweet always contains a user, we specified that with HAS_ONE


<?php


class TwitterUser extends EActiveResource

{


    public static function model($className=__CLASS__)

    {

        return parent::model($className);

    }


    public function rest()

    {

        return array(

            'site'=>'http://api.twitter.com/1',

            'resource'=>'users',

            'idProperty'=>'id',

            'container'=>'user',

            'contenttype'=>'application/json',

            'accepttype'=>'application/xml',

            'embedded'=>array(

                'status'=>array(self::IS_ONE,'Tweet')

            ),

            'fileextension'=>'.xml',

        );

    }


    public function findById($id) {

        $response=$this->getRequest('show/'.$id);

        return $this->populateRecord($response);

    }


}


<php

$publicTweets=Tweet::model()->publicTimeline();

$userOfFirstTweet=$publicTweets[0]->user; //the embedded user object

$username=$userOfFirstTweet->name;

//now let's try to only get the user object without the tweet

$user=TwitterUser::model()->findById($userOfFirstTweet->id);


?>

This is beautiful.

Thanks,

Just added it to the extensions (http://www.yiiframework.com/extension/activeresource/) because I think people will only use it if it is mentioned in extensions.

You can find a small wiki on http://github.com/Haensel/ActiveResource/ and I changed the Configuration() method to rest() as Mike mentioned above.

I hope it will be of use for some people.

greetings,

Haensel

Yup, you’re right.

How are people supposed to know it exists if it’s not there?

Anyway, great work! :lol:

This extension looks great. I’ll give it a try, I was needing something like this some months ago.

Great job.

Regards,

Ricardo

For those interested:

ActiveResource v0.2 is out now:

Better error handling (tracing of error responses returned by the service) and custom exceptions for common http errors (400-407 and 500)

great job , :lol: it ’ s useful for develop SOA website .

This is a great idea. I just developed an extension that uses REST extensively and I wished I had seen this first!

Hi Haensel (we’ve had contact via Twitter about this), is this used somewhere in production modus? Would be very interested to see this… Unfortunately my project (for which I might need this extension) got delayed but the project is still very much alive… Looking forward to using this and improving the Extension.

Hi guys,

@Wouter: Yeah, I remember. Regarding your question: No, it isn’t used in production yet but I am working on a project that should be out at the end of January (hopefully). EActiveResource is used for my entire database layer. I use Neo4j - a graph database and I also developed an object oriented interface on top of active resource to interact with it and it seems quite stable so far but there are still some things that will change which could cause problems later on.

  1. Being able to define a schema. It can be very helpful to be completely schemaless but it can also be important to define the datatype of certain attributes. If using EActiveResource in combination with CActiveForm every attribute will always be treated as string (because the $_POST/$_GET arrays deliver them as such). By defining an attribute as float or integer etc. in a config array this could be solved by explicitly casting the datatypes before saving/sending them to to service.

  2. OAuth and stuff: I never use authentication so I never used OAuth and don’t have any experience with it but I know that this is a very important part. Allowing custom headers for requests seems to be a first step of many to come.

  3. Tests: Probably the most crucial point. The best way would be to create a yii application with an rest api and do the CRUD stuff with active resource but this is a lot of work and I didn’t have the chance to take care of that as of now.

As always. If anyone wants to be part of this project she/he would be very welcome. I suck at Github so there may be some complications regarding push requests and stuff, but I am sure I’ll figure that out some day. Contributions are welcome ;)

Thanks and greetings,

Hannes

P.S.: If you have something that really bugs you it is probably best to use Github to report it or drop me a line via email/Twitter (@Haensel)/G+ (https://plus.google.com/u/0/101755729155174852177) as I can’t check all the different channels out there frequently enough. I totally forgot about this thread for a while

Very good job Haensel.

Would you be interested in sharing your extension? :)

Hi Hannes, thanks for your extensive reply :)

Our situation is as follows: we have a REST API (with authentication based on an APIKEY/SECRET that creates a token with which one can perform API calls) that enables us to get, post, put and delete all the objects that we need in our app(s). On top of that API we are going to build our Yii applications for which we would use this extension. Currently our biggest application runs directly on the database and the rest of our applications are already using the API. However, they are not build in Yii :(

Our project consists of two parts: first we are going to extend our REST API with some API calls and JSON output (currently only XML output) and replace the full top layer to run on the Yii framework and connect to the API. So no more direct database connections! After that we are going to replace the API with something that also runs on the Yii framework. Everything is going to run on AWS for maximum scalability. We will use Elasticache as our caching mechanism together with S3 to store the themes of our customers (each customer has a bucket for static + theme/template files).

  1. This would be very helpful and of course something that would further streamline the way we develop our applications.

  2. Yes, we are planning to implement an oAuth provider as well on our API. This will be one of the last parts of the first half of our project.

  3. TDD is something that has hi priority for me. I guess we can work around it and create our own PHPUnit tests or we will add something to the extension to make this work in a nicer way…

Thanks!

Hi guys,

I updated to V0.5 now. See the extension page for more info. I basically added support for defining a schema with the benefit of having all magic methods working as expected now (same as with CActiveRecord). Example:




class Person extends EActiveResource

     {

     /* The id that uniquely identifies a person. This attribute is not defined

      * as a property      

      * because we don't want to send it back to the service like a name, surname or    

      * gender etc.

      */

     public $id;


     public static function model($className=__CLASS__)

     {

         return parent::model($className);

     }


     /* Let's define some properties and their datatypes

     public function properties()

     {

         return array(

             'name'=>array('type'=>'string'),

             'surname'=>array('type'=>'string'),

             'gender'=>array('type'=>'string'),

             'age'=>array('type'=>'integer'),

             'married'=>array('type'=>'boolean'),

             'salary'=>array('type'=>'double'),

         );

     }


     /* Define rules as usual */

     public function rules()

     {

         return array(

             array('name,surname,gender,age,married,salary','safe'),

             array('age','numerical','integerOnly'=>true),

             array('married','boolean'),

             array('salary','numerical')

         );

     }


     /* Add some custom labels for forms etc. */

     public function attributeLabels()

     {

         return array(

             'name'=>'First name',

             'surname'=>'Last name',

             'salary'=>'Your monthly salary',

         );

     }

 }






/* GET to http://api.example.com/person/1 and populates a single Person model*/

    $person=Person::model()->findById(1);


    /* GET to http://api.example.com/person and populating Person models */

    $persons=Person::model()->findAll();


    /* create a resource

    $person=new Person;

    $person->name='A name';

    $person->age=21;

    $person->save(); POST request. Returns false if the model doesn't validate


    /* Updating a resource (sending a PUT request)

    $person=Person::model()->findById(1);

    $person->name='Another name';

    $person->save(); //PUT request. Returns false if the model doesn't validate


    //or short version

    Person::model()->updateById(1,array('name'=>'Another name'));


    /* DELETE a resource

    $person=Person::model()->findById(1);

    $person->destroy(); //DELETE to http://api.example.com/person/1


    //or short version

    Person::model()->deleteById(1);


    //setting attributes

    $person->attributes=$_POST['Person'];

    if($person->save())

        echo 'yipiie'; //model validated and was saved/updated



I guess this is perfectly what I need for a project of mine where the database shouldn’t be used directly by the application but rather be used through a RESTful API in order to make it possible to switch database (from a relational scheme to a “NoSQL” one) if needed.

I’ll try if your Extension is suitable for this - Thanks for your effort.

The next step would be to integrate API authentication (oAuth?). I have the extension running with the Twitter API and will soon connect it to our own API. Love this extension!

Hi,

Version 0.6 is here. Next to some bugfixes these are the new features.

[list=1]

[*]Added http auth (basic and digest)

[*]Added ssl support

[*]Added url routes for more flexibility

[*]Added support for adding url params

[/list]

Find more info at: http://www.yiiframework.com/extension/activeresource/

@WouterN

The OAuth part is trickier than I thought. I think ActiveResource will be (in most cases) used as part of a system (e.g frontend) interacting with another system (e.g backend) without a user having to grant access. To "speak in OAuth": In such a case the resource owner is the system itself. As far as I understand it 2-legged OAuth should make this possible (without the redirects and stuff) but I found it quite hard to actually find good information on this topic.

Hello,

I am trying to revamp my application by associating it with restful APIs instead of ActiveRecord. This extension would be a great help. However,pls. help me understand where should I define the eactiveresource component/connection like so -

class’=>‘EActiveResourceConnection’,

        'site'=&gt;'...',

Thanks

Neeti