ActiveResource for Yii

Hi nraj,

If you mean how to define the connection itself than you’ll have to add this to your main.php config file somewhere within the ‘components’ part


'components'=>array(

            ...other components like user etc...,

            'activeresource'=>array(

                'class'=>'EActiveResourceConnection',

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

                'contentType'=>'application/json',

                'acceptType'=>'application/json',

                'queryCacheID'=>'SomeCacheComponent'),

           ...other components....,

        

Hi, very useful.

But I have a question, can I had extra API methods to the resource and call them transparently?

Hi,

Yes, you can do that. Let’s say your api allows a request like GET http://api.yoursite.com/User?active=true to return a list of active users. In your User resource you could then add:




public function findUsersByState($state=true)

    {

            $params=array('active'=>$state);

            $response=$this->query('collection','GET',$params); //Send a get request to the "collection" which is defined in your routes() method with the given params array. It defaults to "User" meaning an uri like http://api.yoursite.com/User

            return $this->populateRecords($response->getData()); //create an array of User models from the returned result

    }



Now try this to get all non active users




        $nonActiveUsers=User::model()->findUsersByState(false);



Didn’t try that one and coded in this editor so there may be typos, but I hope you get the idea :)

cheers,

Hannes

Hi,

i’m working to expand features of EActiveResource to work with sessions (If you are interested I can send the code, when terminated), and I found an error:

In EActiveResourceRequest::setCookies you call


$params = $this->cleanPost($values);

but isn’t undefined. Whah should make this function?

tnx

mp

Second question:

why did you not use (implement) the same method like "getPrimaryKey" or "findByPrimaryKey", and instead you have add method like "getID" or "findById"?

scuse me for bad english…

:)

tnx

mp

This is great stuff!!

Is there a way to represent nested objects with your properties? In other words if I have an invoice, and it include 0…N line items, where LineItem is a separate object, how would I go about representing that in your extension?

thanks for putting the effort into this!

Sorry to all of you for answering that late. As always, it is not a good idea to ask in here if it is urgent, better use Twitter or Github (for issues)

@mario_ar_gas: Simple answer: Because there is no primary key in an REST API :)

@darrylivan: Not yet. I know that some APIs do that but I don’t plan to add this feature soon. For the most part restful relations are defined via “subcollections” like /post/1/author etc.

UPDATE

Hi guys and girls. I did some MASSIVE work on ActiveResource and came up with some nice goodies. They are not yet commited to the master branch so if you want to check them out use the "QueryCriteria" branch on Github to download: https://github.com/Haensel/ActiveResource/tree/QueryCriteria

The reason I am posting this is that I would love to have some brave people testing out the new features before committing to the master branch which will break older code in some cases (especially where custom url parameters where used). I am sure there are some bugs in there. As you can imagine this is a HUGE chunk of code for one person, so heads up

Here’s a list of the new features:

1.) Relations: Yes, finally you are able to define related models that are reachable via subcollections.




    public function routes()

    {

        return CMap::mergeArray(

                parent::routes(),

                array(

                    'posts'=>':site/:resource/:id/posts'

                )

        );

    }


    public function relations()

    {

        return array(

            'posts'=>array(self::HAS_MANY,'Post','posts')

        );

    }


    //now somewhere in your app


    $posts=User::model()->findById(1)->posts

2.) Scopes:


//default scopes always used with finders

    public function defaultScope()

    {

        return array(

            'condition'=>'published=true'

        );

    }


    public function scopes()

    {

        return array(

            'topTen'=>array(

                 'limit'=>10

                 'order'=>'created_at'

            )

        );

    } 

3.) Criteria objects


$criteria=new EActiveResourceQueryCriteria(array(

        'condition'=>'name=:username'

        'limit'=>10

        'offset'=>1

        'order'=>'name'

        'params'=>array(':username'=>'haensel*')

    ));


    $models=User::model()->findAll($criteria);


    //GET to api.example.com/user?name=haensel*&page=1&count=10&order=name

4.) Because of the criteria objects you can now create EActiveResourceDataProvider objects + EActiveResourcePagination objects that will work with CListView and GridView etc. + pagination


$dataProvider=new EActiveResourceDataProvider('Post',$criteria);

    $dataProvider=new EActiveResourceDataProvider(Post->published(),$criteria);

Please let me know what you think. What is bad, what is good? Any problems? Please let me know! Thank you all!

Hannes

Hello Hannes

First of all, thanks for this great extension! I am trying your updated code, and have some trouble regarding relations. Actually it may be due to the structure of the api I am connecting to, but I would like to hear your opinion.

I have a state that contains several municipalities. When I call api/state/[stateId], I get a structure like this:




<state>

    <id>...</id>

    <name>...</name>

    <country>

        <id>...</id>

        <name>...</name>

    </country>

</state>



As you see there are no municipalities listed here. But if I call api/state/[stateId]/municipalities I will get a list of the municipalities in the current state, structured like this:




<municipality>

    <id>...</id>

    <name>...</name>

    <state>

        <id>...</id>

        <name>...</name>

        <country>

            <id>...</id>

            <name>...</name>

        </country>

    </state>

</municipality>



So, my question is: is it possible to use the relations as described in your previous post when the api has a structure like this? It would really simplify my code being able to perform calls like this: State::model()->findById([stateId])->municipalities. Thanks in advance!

Hi guys,

I run into some problems… I’m using the queryCriteria branch in my project.

I have a client class




class Client extends EActiveResource

{

   

    public $id;

    public $numFound=0;


    public static function model($className=__CLASS__)

    {

        return parent::model($className);

    }


       public function rest()

    {

        return CMap::mergeArray(

            parent::rest(),

            array(

                'resource'=>'client',

                'idProperty'=>'id',

            )

        );

    }


    /* Let's define some properties and their datatypes*/

    public function properties()

    {

        return array(

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

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

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

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

          

        );

    }


    /* Define rules as usual */

    public function rules()

    {

        return array(

            array('name,gender,birthday','safe'),

            

        );

    }


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

    public function attributeLabels()

    {

        return array(

            'name'=>'First name',

            'birthday'=>'Birthday',

            'gender'=>'Sex',

            'id'=>'ID',

        );

    }


    public function populateRecord($attributes,$callAfterFind=true)

    {

       //code to populate record

       

    }


    

    public function populateRecords($data,$callAfterFind=true,$index=null)

    {


       //extract params  foreach  populate record and add to class list


    }




}



in my view file (just to test) I create an EACtiverResourceCriteria which I use to create an EActiveResourceDataProvider which I can use to fill u my gridview (bootstrap gridview -> but I’ve tried the CGridView as well with the same result…)




$criteria=new EActiveResourceQueryCriteria(array(

        'condition'=>'name=:username',

        'params'=>array(':username'=>'*')

    ));

$clients=Client::model()->findAll($criteria); //this is working

$dataProvider=new EActiveResourceDataProvider('Client',array('criteria'=>$criteria));


$this->widget('bootstrap.widgets.TbGridView',

    array(

        'type'=>'striped bordered condensed',

        'dataProvider'=>$dataProvider,

        //'filter' => $model,

        'columns'=>array(

            array('name'=>'name', 'header'=>'Name'),

            array('name'=>'gender', 'header'=>'Sex'),

            array('name'=>'language', 'header'=>'Language'),

            array('name'=>'birthday', 'header'=>'Birthday'),

        ),

    ));




the first error was.




Client and its behaviors do not have a method or closure named "tableName".



So I’ve added the following to my Client class




public function tableName()

    {  

        return 'Client';

    }



Then I received…




Client and its behaviors do not have a method or closure named "getDbConnection".



So I’ve implmented a getDbConnection method that returns null… But then the system tries to get the schema from the null object and the errors keep on comming.

Can someone help me ?

How can I implement the dataprovider for EActiveResource ?

thanks

Hi,

The problem is not with the EActiveResource class. It is with EActiveResourceDataProvider using CSort as default for sorting. And CSort itself uses CActiveRecord internally (i.e. tries to dynamically instantiate a CActiveRecord object with the name of your EActiveResource class, which, as you see, fails).

I have written an extension class of CSort (ESort, see attached files) and changed EActiveResourceDataProvider to use it as default. Both classes are attached to this post. Just copy them in your EActiveResource directory.

Please check if this solves your problem.

Hi Haensel,

This is a great extension. I’ll be using this in my project. But am stuck with the CRUD operations with gii or giix with ActiveResource. Is there a way that we can tweak or setup so that we can use CRUD generator? We need this features desperately… :-[

Hello,

How can I use ClistView with EActiveResourceDataProvider?

Can you please show me a sample?

Regards

Hi Haensel,

I am getting this CException…


Property "EActiveResourceQueryCriteria.agency_id" is not defined. 

when tried to create a query using the agency_id field on my Request model.

This is the code on my Request model…


public function findRequestByAgency($agency_id)

    {

            $params=array('agency_id'=>$agency_id);

            $response=$this->query('collection','GET',$params); 

            return $this->populateRecords($response->getData()); 

    }

//GET localhost/api/v1/request/search?receivingAgencyId=11



This is an awesome extension by the way.

Cheers mate!