restfullyii

Adds RESTFul API to your Yii application
46 followers

Update Version 1.10 Bug Fixes

Version 1.6

Version 1.5

  • Use with javascript (See validateAjaxUser in ERestController)
  • Record count now included in JSON output
  • Query String filter/sort/limit/offset :
/api/post/?limit=2&offset=1&sort=[{'property':'title','direction':'ASC'}]&filter=[{'property':'title', 'value':'some value'},{'property':'comment', 'value':'You need a REST'}]
  • Save and display nested data sets:
{
    "data": {
        "presentation": [
            {
                "id": "41", 
                "author_email": "john.smith@somesite.com", 
                "author_name": "JSmith", 
                "description": "this is a great presentation", 
                "password": "12345", 
                "slides": [
                    {
                        "id": "17",
                        "content": "c4", 
                        "created": "1347972285", 
                        "description": "d3", 
                        "image_id": null, 
                        "options": null, 
                        "title": "t35795", 
                        "updated": "1347972285"
                    }, 
                    {
                        "id": "18", 
                        "content": "c4", 
                        "created": "1347972285", 
                        "description": "d4", 
                        "image_id": null, 
                        "options": null, 
                        "title": "t45795", 
                        "updated": "1347972285"
                    }
                ], 
                "slug": "shoot123", 
                "title": "my present_test_2", 
                "updated": "1349289196"
            }
        ], 
        "totalCount": "2"
    }, 
    "message": "Records Retrieved Successfully", 
    "success": true
}

Version 1.2

  1. Unit Test Class
  2. Easier use with javascript/AJAX
  3. Added missing loadModel method
  4. Bug Fixes

Version 1.1 has been refactored to be used as an extension (not module).

Adds RESTFul API to your Yii application.

Lets say you have a controller named `PostController'. Your standard routes will look as they always do, ie /post/actionName .

RESTFullYii adds a new set of RESTFul routes to your standard routes, but prepends `/api' .

So if you apply RESTFullYii to the `PostController' you will get the following new routes by default (You can override their behavior in your controller).

[GET] http://yoursite.com/api/post/ (returns all posts)

[GET] http://yoursite.com/api/post/1 (returns post with PK=1)

[POST] http://yoursite.com/api/post/ (create new post)

[PUT] http://yoursite.com/api/post/1 (update post with PK=1)

[DELETE] http://yoursite.com/api/post/1 (delete post with PK=1)

Requirements

Yii 1.8 or above

Installation

  1. Place restfullyii into your protected/extensions directory

  2. In your main.php config be sure to include 'ext.restfullyii.components.*' in your `import' array.

import'=>array(
    'ext.restfullyii.components.*',
),
  1. You will need to add the routes below to your main.php They should be added to the beginning of the rules array.
'api/<controller:\w+>'=>array('<controller>/restList', 'verb'=>'GET'),
'api/<controller:\w+>/<id:\w+>'=>array('<controller>/restView', 'verb'=>'GET'),
'api/<controller:\w+>/<id:\w+>/<var:\w+>'=>array('<controller>/restView', 'verb'=>'GET'),
array('<controller>/restUpdate', 'pattern'=>'api/<controller:\w+>/<id:\d+>', 'verb'=>'PUT'),
array('<controller>/restDelete', 'pattern'=>'api/<controller:\w+>/<id:\d+>', 'verb'=>'DELETE'),
array('<controller>/restCreate', 'pattern'=>'api/<controller:\w+>', 'verb'=>'POST'),
array('<controller>/restCreate', 'pattern'=>'api/<controller:\w+>/<id:\w+>', 'verb'=>'POST'),
 
'<controller:\w+>/<id:\d+>'=>'<controller>/view',
'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',

Alternatively you can choose to use the included routes.php.
Then your main.php config for `urlManager' should look like this:

'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>require(dirname(__FILE__).'/../extensions/restfullyii/config/routes.php'),
),
  1. Setting up the controller: (This applies to controllers for which you you would like to add RESTFull routes) Change your controller class so that it extends ERestController:

    class PostController extends ERestController{..}

You will need to merge your fileters & accessRules methods with the parent methods here. To do that you simply change the name of these methods by prepending an underscore ("_"). So in your controller you will need to change the following:

`public function filters()'

-becomes-

`public function _filters()'

`public function accessRules()'

-becomes-

`public function _accessRules()'

Security

The 'username' and 'password' are currently hardcoded as Const's in `ERestController'.

Const USERNAME = 'admin@restuser';
Const PASSWORD = 'admin@Access'

At a minimum you will want change these values. To create a more secure Auth system modify the 'filterRestAccessRules' method in 'ERestController'. This should be straight forward.

Usage

Sample Requests:

Verbs (GET, PUT, POST, DELETE)

GET

  List
      curl -i -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" http://yii-tester.local/api/sample/

    View

      curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" http://yii-tester.local/api/sample/174

  PUT
    Update
      curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" -H "X-HTTP-Method-Override: PUT" -X PUT -d '{"id":"174","name":"Five.1 Alive one ever Updated Again","desc":"It really is or should be at an honor","notes":"this is a note"}' http://yii-tester.local/api/sample/174

  POST

    Create
      curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" -X POST -d '{"id":"175","name":"Six Alive one ever Updated Again","desc":"It really is or should be at an honor","notes":"this is a note"}' http://yii-tester.local/api/sample

      curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" -X POST -d '[{"id":"175","name":"Six Alive one ever Updated Again","desc":"It really is or should be at an honor","notes":"this is a note"},{"id":"176","name":"First.3 one ever Updated Again","desc":"It really is or should be at an honor","notes":"this is a note"}]' http://yii-tester.local/api/sample


  Delete

      curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" -H "X-HTTP-Method-Override: DELETE" -X DELETE http://yii-tester.local/api/sample/175

You may also optionally create custom REST methods in your controllers.

You must prefix your method with doCustomRest & the verb.

For GET request you use doCustomRestGet: EG public function doCustomRestGetOrder($var=null)

  GET
   curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" http://yii-tester.local/api/sample/order

   curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" http://yii-tester.local/api/sample/order/2

Similarly you can POST' to a custom function. You must prefix your method withdoCustomRestPost(same is true for PUTdoCustomRestPutOrder($data)')

EG `public function doCustomRestPostOrder($data)`
  POST
    curl -l -H "Accept: application/json" -H "X_REST_USERNAME: admin@restuser" -H "X_REST_PASSWORD: admin@Access" -X POST -d '{"id":"2","order":"French Fries"}' http://yii-tester.local/api/sample/order

To change behavior of default RESTFul actions you can simply override any of the following methods in your controller:

Sub-Resources    When working with 'many to many' relations you now have the ability to treat them as sub-resources.  

Consider:  

URL Format: http://mysite.com/api/<controller>/<id>/<many_many_relation>/<many_many_relation_id>
 
Getting player 3 who is on team 1  
or simply checking whether player 3 is on that team (200 vs. 404)  
GET /team/1/players/3  
 
getting player 3 who is also on team 3  
GET /team/3/players/3  
 
Adding player 3 also to team 2  
PUT /team/2/players/3  
 
Getting all teams of player 3  
GET /player/3/teams  
 
Remove player 3 from team 1 (Injury)
DELETE /team/1/players/3  
 
Team 1 found a replacement, who is not registered in league yet  
POST /player  
 
From payload you get back the id, now place it officially to team 1  
PUT /team/1/players/44  

Changing Default RestFullYii Behavior
To change behavior of default RESTFul actions you can simply override any of the following methods in your controller:

public function isPk($pk)
 
 public function validateAjaxUser($action)
 
 public function doRestList()
 
 public function doRestView($id)
 
 public function doRestViewSubResource($id, $subResource, $subResourceID=null)
 
 public function doRestUpdate($id, $data)
 
 public function doRestUpdateSubResource($id, $subResource, $subResourceID)
 
 public function doRestCreate($data)
 
 public function doRestDelete($id)
 
 public function doRestDeleteSubResource($id, $subResource, $subResourceID)

Resources

Total 20 comments

#13121 report it
evan108108 at 2013/05/06 10:26am
@acy: Bug Fixes

Hi acy,

thanks again! version 1.12 should resolve the issue. Now Put and Get response data should in insync.

#13106 report it
acy at 2013/05/05 08:01am
data structure of PUT and GET still different

Thanks for the fix evan! The PUT request returns the missing relations now.

But one thing is still weird. The responses of a GET and a PUT have a different structure (see "person"):

GET /api/person/123/

{
  "success": true,
  "message": "Record Retrieved Successfully",
  "data": {
    "totalCount": 1,
    "person": 
    {
      "id": "123",
      "name": "john",
      "update": {
        "id": "1",
        "version": "1.1.1"
      },
      "group": [
        {
          "id": "2",
          "name": "does"
        }
      ],
      "team": [
        {
          "id": "3",
          "comment": "Yay"
        }
      ],
    }
  }
}

PUT /api/person/123/

{
  "success": true,
  "message": "Record Updated",
  "data": {
    "totalCount": 1,
    "person": 
    [ // <-- NEW
        {
          "id": "123",
          "name": "john",
          "update": {
            "id": "1",
            "version": "1.1.1"
          },
          "group": [
            {
              "id": "2",
              "name": "does"
            }
          ],
          "team": [
            {
              "id": "3",
              "comment": "Yay"
            }
          ],
        }
    ]  // <-- NEW
  }
}

This happens only with 1.11 (not with 1.09)

Thanks a lot for your efforts!

#12946 report it
evan108108 at 2013/04/22 05:02pm
@heal

You can use it without active record. Just override the default actions in your controller IE doRestList, doRestView, etc...

#12945 report it
evan108108 at 2013/04/22 05:01pm
@acy: Bug Fixes

Thanks you are correct. Version 1.11 should address these issues. As well as add a new feature which allows you to use Scenarios to control field output.

#12918 report it
heal at 2013/04/20 09:53am
using without AR

Hello, how can I use this extension with CModel? (I dont want to use ActiveRecord.) Thank You!

#12851 report it
acy at 2013/04/15 08:50pm
Data structure changed after upgrade from 1.9 to 1.10

I upgraded from 1.9 to 1.10 and it broke my application because the structure of the PUT response changed:

PUT {"name":"john"}

Version 1.9:

{
  "success": true,
  "message": "Record Updated",
  "data": {
    "totalCount": 1,
    "person": {
      "id": "123",
      "name": "john",
      "update": {
        "id": "1",
        "version": "1.1.1"
      },
      "group": [
        {
          "id": "2",
          "name": "does"
        }
      ],
      "team": [
        {
          "id": "3",
          "comment": "Yay"
        }
      ],
    }
  }
}

Version 1.10:

{
  "success": true,
  "message": "Record Updated",
  "data": {
    "totalCount": 1,
    "person": [
      {
        "id": "123",
        "name": "john"
      }
    ]
  }
}

Why is this happening?

Most notable is the change of the data type object becomes an array and I didn't even touch the database model etc. Also the many to many relations aren't visible anymore.

The structure of the GET output didn't change by the way.

I think I can handle the change but is this intentionally? I probably need to send another GET request because now I get less data than before.

#12506 report it
evan108108 at 2013/03/24 10:00pm
@stepanic & @Daantje

I would like to thank the both of you for finding these bugs and submitting solutions! The new version 1.10 has your changes rolled in.

#12500 report it
stepanic at 2013/03/24 12:31pm
ERestController.php -> public function triggerCustomRestPut

Should be: public function triggerCustomRestPut($method, $vars=array()) { $method = 'doCustomRestPut' . ucfirst($method);

    if(method_exists($this, $method))
    {
        if(count($vars) > 0)
            $this->$method($this->data(), $vars);
        else
            $this->$method($this->data());
    }
    else
    {
        throw new CHttpException(500, 'Method or Sub-Resource does not exist.');
    }

}

Instead of: public function triggerCustomRestPut($method, $vars=array()) { $method = 'doCustomRestPut' . ucfirst($method);

    if(method_exists($this, $method))
    {
        if(count($vars) > 0)
            $this->$method($this->data(), $vars);
        else
            $this->$method($this->data());
    }        
   throw new CHttpException(500, 'Method or Sub-Resource does not exist.');

}

If throw new CHttpException is out of ELSE statement, Custom PUT method will be never called.

#12488 report it
evan108108 at 2013/03/23 09:13am
@Tod C

The link to Git works for me. What happens when you click it?

#12487 report it
Todd C at 2013/03/23 08:57am
Git repo link is wrong

The link to your git repo is wrong. Might want to fix it.

#12356 report it
Daantje at 2013/03/15 11:23am
@evan108108

This is what I do: I post an update: {id:1,asset_id:3} The original code gets the model with all the related/nested models. So the model has now this: {id:1,asset_id:2,assets:{id:2,name:'john'}} (old record before save). Than there is a save, it saves the old asset_id, not the new one given in the $data param. But only when I have relations in the model. So what rest api returns is this {id:1,asset_id:2,assets:{id:2,name:'john'}} Other fields are saved correctly, only the indexed fields (in this case asset_id) are overwritten by the original value.

#12353 report it
evan108108 at 2013/03/15 09:32am
@Daantje

Can you give me a specific example? What exactly to you mean by "The requested relation id was over written by the original relation id"?

#12340 report it
Daantje at 2013/03/14 03:25pm
Bug with update and nested relations

I found a bug when updating a record with nested relations. The requested relation id was over written by the original relation id. This will fix that.

Changed in ERestController.php:

/**
* Helper for loading a single model
*/
protected function loadOneModel($id, $nested=true) 
{
    if($nested)
        return $this->getModel()->with($this->nestedRelations)->findByPk($id);
    else
        return $this->getModel()->findByPk($id);
}

And this in the same file:

/**
 * This is broken out as a sperate method from actionResUpdate 
 * To allow for easy overriding in the controller
 * and to allow for easy unit testing
 */ 
public function doRestUpdate($id, $data) 
{       
    $model = $this->saveModel($this->loadOneModel($id,false), $data);
    $this->outputHelper(
        'Record Updated',
        $this->loadOneModel($id),
        1
    );
}
#12206 report it
evan108108 at 2013/03/06 09:13am
@stepanic

I don't think 'findByPk' with throw an exception when the PK is not found. I believe it just returns NULL...

#12205 report it
stepanic at 2013/03/06 07:51am
Problem FIX-ex

If you have Model with Nested model, than in ERestController.php method loadOneModel should be: protected function loadOneModel($id) { try { $a=$this->getModel()->with($this->nestedRelations)->findByPk($id); } catch(Exception $ex) { $a=null; }

    if ($a)
    {
        return $a;
    }
    else
    {
        throw new CHttpException(404, 'Record Not Found');
    }

}

Otherwise PHP script return Error 500 Internal server error, for models who are not recorded in database :D

Enjoy, restfullyii -> fantastic extension

#11898 report it
imehesz at 2013/02/11 10:28pm
doCustomRestGet working

Evan,

I had a custom beforeAction() in my controller, and that threw off the "REST" - (haha)

thanks for your help and for this extension!

--iM

#11894 report it
evan108108 at 2013/02/11 01:43pm
Re: doCustomRestGet not doing it .

Hi imehesz,

I ran a test on my local and I can't replicate your problem, but I will go through the steps and you can confirm if this is what you are doing.

In your Controller (Lets say ItemController) add a method like this:

public function doCustomRestGetTesting($vars=null)
{
   $this->renderJson(array('vars'=>$vars));
}

So your GET request will look like: http://mysite/api/item/testing/cool

This should return: { "vars": [ "cool", null ] }

You can add a second param like: http://mysite/api/item/testing/cool/man

This should return: { "vars": [ "cool", "man" ] }

I hope that helps. If its still not working I would be happy to take a look at your code and give you a hand...

#11877 report it
imehesz at 2013/02/10 09:00pm
doCustomRestGet not doing it ...

hello Evan,

First off, I love this extension, used it many times. Keep up the good work.

So far, I was able to use your code as is, but now I need to create some custom GET requests, and I can't seem to figure out what I'm missing...

I tried to implement your example: doCustomRestGetOrder($var=null) in my controller, but I keep getting 404 errors:

{"success":false,"message":"The requested page does not exist.","data":{"errorCode":404}}

Do I need to set something specific in my routes/accessRules/filters?

I tried to troubleshoot it a little bit, noticed that you call triggerCustomRestGet() in actionRestView(), but the thing actually breaks before it gets to that function :/

thanks for your help, --iM

#11633 report it
evan108108 at 2013/01/24 12:12am
Please Clarify

Not sure what the previous two comments are referencing? This is not at all how you would use this extension. If you want to add a custom RESTFull route to your API its as simple as adding a method to your controller.

public function doCustomRestGetMyRoute($vars=array())
{
   //some logic
   $this->renderJson(array(
     'success'=>true, 
     'message'=>'I called a custom route', 
     'data'=>$vars;
   ));
}

You now have a route that looks like this:

http://yoursite.com/api/<controller>/myRoute/<optional_var1>/<optional_var2>
#10608 report it
smyiia at 2012/11/07 11:23am
example Delete

public function actionDelete($id) { $uri = 'http://localhost/xxx/app/index.php/api/table03/' . $id; $ch = curl_init($uri); curl_setopt_array($ch, array( CURLOPT_HEADER => FALSE, CURLOPT_HTTPHEADER => array('X-Rest-Username: admin@restuser', 'X-Rest-Password: admin@Access'), CURLOPT_RETURNTRANSFER => true, CURLOPT_VERBOSE => 1, CURLOPT_CUSTOMREQUEST => 'DELETE' )); $out = curl_exec($ch); curl_close($ch);

    echo $out;
}

Leave a comment

Please to leave your comment.

Create extension