Update Version 1.10 Bug Fixes
Version 1.6
Version 1.5
/api/post/?limit=2&offset=1&sort=[{'property':'title','direction':'ASC'}]&filter=[{'property':'title', 'value':'some value'},{'property':'comment', 'value':'You need a REST'}]
{ "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
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)
Yii 1.8 or above
Installation
Place restfullyii into your protected/extensions directory
In your main.php config be sure to include 'ext.restfullyii.components.*' in your `import' array.
import'=>array( 'ext.restfullyii.components.*', ),
'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'), ),
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.
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)
Total 20 comments
Hi acy,
thanks again! version 1.12 should resolve the issue. Now Put and Get response data should in insync.
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/
PUT /api/person/123/
This happens only with 1.11 (not with 1.09)
Thanks a lot for your efforts!
You can use it without active record. Just override the default actions in your controller IE doRestList, doRestView, etc...
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.
Hello, how can I use this extension with CModel? (I dont want to use ActiveRecord.) Thank You!
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:
Version 1.10:
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.
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.
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.
The link to Git works for me. What happens when you click it?
The link to your git repo is wrong. Might want to fix it.
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.
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"?
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:
And this in the same file:
I don't think 'findByPk' with throw an exception when the PK is not found. I believe it just returns NULL...
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
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
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:
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...
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
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.
You now have a route that looks like this:
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);
Leave a comment
Please login to leave your comment.