Last weekend, I spent 2 frustrating days trying to get a RESTful application up and running with CakePHP! Once again, the good folks at CakePHP have documentation, but they miss some important things that caused me massive headaches. After this long and painful process was finished, I promised myself to write a tutorial on how to create a RESTfull application in CakePHP and also how to write a simple client to interact with the RESTfull application. You can download the full tutorial here and you are free to modify it however you like. The tutorial is based on CakePHP 2.4 (Please note that I am not changing the default CakePHP theme. So below is what the index page looks like:)
Step1: The Database Structure
For our REST application, we will be creating a Phones listing application. The application will allow users to list/edit/view/delete and add phones. Here is the database structure:
Let’s start by creating the phones database table. The phones table contains a phone’s name, manufacturer and description. Here is what the final phones table looks like:
CREATE TABLE phones ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), manufacturer VARCHAR(50), description TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL );
Next I populate the database with 4 entries. (The top 4 phones at moment)
INSERT INTO phones (name,manufacturer,description,created) VALUES ('iPhone 5s', 'Apple', 'The iPhone 5s may look a lot like its predecessor. But with a faster new processor, a fingerprint sensor, and an improved camera flash, it is a serious upgrade.', NOW()); INSERT INTO phones (name,manufacturer,description,created) VALUES ('Nexus 5','Google', 'Running the latest version of Android, the Nexus 5 really shows off the best aspects of Googles mobile OS. There are few reasons not to pick the Nexus 5 as your next Android phone.', NOW()); INSERT INTO phones (name,manufacturer,description,created) VALUES ('One','HTC', 'With its stellar design, great camera, and hardy processor, the HTC One is the phone to beat.', NOW()); INSERT INTO phones (name,manufacturer,description,created) VALUES ('Galaxy S4','Samsung', 'The Samsung Galaxy S4 is a stellar Android phone held back by boring design and half-baked features.', NOW());
Step 2: Create The Phones Model, Controller and View
The next thing we need to do is create the phones model controller and view. This is pretty standard and basic stuff in Cake. Although I have omitted the view files, you can view the phones model and controller below. The views files can be found in the tutorial download itself.
Phones Model
class Phone extends AppModel { }
Phones Controller
class PhonesController extends AppController { public $helpers = array('Html', 'Form'); public $components = array('RequestHandler'); public function index() { $this->set('phones', $this->Phone->find('all')); } public function add() { if ($this->request->is('post')) { $this->Phone->create(); if ($this->Phone->save($this->request->data)) { $this->Session->setFlash(__('The phone has been saved.')); return $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('Unable to add your phone.')); } } public function view($id = null) { if (!$id) { throw new NotFoundException(__('Invalid phone')); } $phone = $this->Phone->findById($id); if (!$phone) { throw new NotFoundException(__('Invalid phone')); } $this->set('phone', $phone); } public function edit($id = null) { if (!$id) { throw new NotFoundException(__('Invalid phone')); } $phone = $this->Phone->findById($id); if (!$phone) { throw new NotFoundException(__('Invalid phone')); } if ($this->request->is(array('phone', 'put'))) { $this->Phone->id = $id; if ($this->Phone->save($this->request->data)) { $this->Session->setFlash(__('Your phone has been updated.')); return $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('Unable to update your phone.')); } if (!$this->request->data) { $this->request->data = $phone; } } public function delete($id) { if ($this->request->is('get')) { throw new MethodNotAllowedException(); } if ($this->Phone->delete($id)) { $this->Session->setFlash( __('The phone with id: %s has been deleted.', h($id)) ); return $this->redirect(array('action' => 'index')); } } }
Step 3: Setup Restfullness
Now, its time to make our phones application “RESTful”. The first thing we need to do is modify our routes.php file by adding 2 lines.
//In app/Config/routes.php... Router::mapResources('phones'); Router::parseExtensions();
The first line calls the function mapResources() which specifies the controllers that should support REST. Two things need to be noted here:
- It requires the url-ized version of your controller as a parameter. So “Phones” becomes “phones” and if you had a controller named “LiveCustomers”, it would become “live_customers”.
- I didn’t put the Phones controller but instead a controller called RestPhones. This will be explained later..
The second line tells Cake to start parsing extensions. So CakePHP will now be able to respond to actions that have extensions. By default, CakePHP will support XML, JSON and RSS as extensions. So, clients will be able to call for a JSON output by calling action.json and an XML output by calling action.xml. It’s actually pretty cool.
One important thing to remember is that the piece of code above to support REST routing must be defined before this line:
require CAKE . 'Config' . DS . 'routes.php';
Not doing so will cause you several hours of painful debugging. Trust me, it happened to me…
Step 4: Understanding CakePHP REST routes
If you know anything about REST, then you are aware that it only supports standard HTTP requests such as POST, GET, PUT and DELETE. What CakePHP does is match various controller actions to REST routes and also to specific HTTP requests. (This is actually pretty ingenious!) So, if you had a controller that is configured to accept REST calls, then you have to follow the convention and specify the proper HTTP format for the proper controller action. For example, to list all the phones in our database, we need to create a controller action called index() and its url will have to be /phones.format and it will also only accept GET requests. Here is the standard table ripped out of Cake 2.0 Book:
HTTP format |
URL.format |
Controller action invoked |
---|---|---|
GET |
/rest_phones.format |
RestPhonesController::index() |
GET |
/ rest_phones/123.format |
RestPhonesController::view(123) |
POST |
/ rest_phones.format |
RestPhonesController::add() |
PUT |
/ rest_phones/123.format |
RestPhonesController::edit(123) |
DELETE |
/ rest_phones/123.format |
RestPhonesController::delete(123) |
POST |
/ rest_phones/123.format |
RestPhonesController::edit(123) |
As The Cake book mentions: These routes are HTTP Request Method sensitive.. In other words, these routes are sensitive to the HTTP request method that you specify. So, trying to request a post to the index() controller will generate an error. If you are not happy with the default REST routes, cake allows you to change them as well. (I personally think that the default CakePHP routes are perfect and should not be changed…) Once again, I am using a controller called RestPhonesController that I still have not explained. Well lets explain why I am using this mystery controller…
Step 4: Understanding CakePHP 2.0 REST limitations
In order to explain why I have the RestPhonesController class, we need to talk about limitations that CakePHP has:
1) MapResources() does not support prefixes!!!: A clean API for REST should have clean URLs! So, in the case of my phones controller’s index function, it would be nice if I could create a function called index() for regular web viewing and then rest_index() for REST calls to the application. Then using, prefix routing, I could tell CakePHP that any controller in PhonesController that are prefixed by rest_ should get converted to /rest/ in their URL. So rest_view() would map to the url /rest/view/. However, CakePHP 2.0’s mapeResources function does not allow this! In fact, it took me a full day to find this out!!! But once I discovered this, I found other fellow developers who were bitching at the same issue… (Thanks for the discovery, Rudy Lee!) Apparently, this issue will be fixed for CakePHP 3.0 but I cant wait that long. There is also a hack out there involving modifying the core of CakePHP but I am against hacking the core of any framework. You should never hack your framework to get things to work…
2) You must explicitly specify which HTTP actions are allowed to pass!!!: As all good web developers know, you should only use https and secured connections to interact with REST services. This protects your data and protects you from man-in-the-middle attacks. In order to do this with CakePHP, you need to enable CakePHP’s security component. However, This was another half-day wasted in discovering that CakePHP’s security component does not play nice with REST clients. SecurityComponent needs to know which HTTP requests that it is allowed to accept from clients! By default, Cake’s Security component will “BLACKHOLE” all HTTP requests unless you explicitly specify which ones should pass through. (I discovered this one thanks to MetZ.)
So how do we get around these problems…?
Step 5: Create A RESTful Controller
The reason why RestPhonesController exists is that it allows me to provide a clean REST service that is separate from the regular application. It also allows me to add the prefix convention that I want so that I can separate the regular browser calls from the REST API calls. RestPhonesController basically uses the same model as PhonesController (Phones model) but it can be customized for whatever limitations or additions that we want to give our RESTful service. For example, imagine that on the web application, you can edit any phone but on the RESTful service, you only want the user to be able to edit a limited number of phones… By having RestPhonesController, I also follows all of CakePHP’s REST routes. So now, I have actions that are REST-specific and also follow proper routing. Here is the full code for RestPhonesController:
class RestPhonesController extends AppController { public $uses = array('Phone'); public $helpers = array('Html', 'Form'); public $components = array('RequestHandler'); public function index() { $phones = $this->Phone->find('all'); $this->set(array( 'phones' => $phones, '_serialize' => array('phones') )); } public function add() { $this->Phone->create(); if ($this->Phone->save($this->request->data)) { $message = 'Created'; } else { $message = 'Error'; } $this->set(array( 'message' => $message, '_serialize' => array('message') )); } public function view($id) { $phone = $this->Phone->findById($id); $this->set(array( 'phone' => $phone, '_serialize' => array('phone') )); } public function edit($id) { $this->Phone->id = $id; if ($this->Phone->save($this->request->data)) { $message = 'Saved'; } else { $message = 'Error'; } $this->set(array( 'message' => $message, '_serialize' => array('message') )); } public function delete($id) { if ($this->Phone->delete($id)) { $message = 'Deleted'; } else { $message = 'Error'; } $this->set(array( 'message' => $message, '_serialize' => array('message') )); } }
One important thing that you will notice in RestPhonesController is that I added the following line to call the RequestHandlder component of CakePHP:
public $components = array('RequestHandler');</p>
The requesthandler is the component that will handler all REST HTTP requests that are received and perform the appropriate action based on the provided url. So, for example, A POST to /rest_phones.json will create a new Phone and return JSON data to the client. A call GET to /rest_phones.xml will get all the Phones in the system and return XML data to the client. You can refer to the table above to see what URL path should be used for what HTTP action..
Step 5: Update AppController
Now, in order to split requests coming in, I have modified AppController’s beforeFilter() method to determine if we are rest controller or not. If we are the RestPhonesController, we tell the security component to unlock the various REST actions for the HTTP requests, otherwise. Here is my code for beforeFilter:
public function beforeFilter() { if(in_array($this->params['controller'],array('rest_phones'))){ // For RESTful web service requests, we check the name of our contoller $this->Auth->allow(); // this line should always be there to ensure that all rest calls are secure /* $this->Security->requireSecure(); */ $this->Security->unlockedActions = array('edit','delete','add','view'); }else{ // setup out Auth $this->Auth->allow(); } }
Note that although I am using the Auth component, it allows all actions! You will most likely need to modify this to suit your needs. You will have to modify the provided code to support authentication functions such as login, logout, etc…
Step 6: Setup a Test Client
I took the lazy approach with my test client and simply integrated it directly into the same application. I did this by creating a controller called ClientController. This controller uses CakePHP’s HttpSocket class to make HTTP request to the REST service. The client allows you to add/edit/view/delete phones by making the proper REST calls. For every call, the client will display the response code as well as the response content for any HTTP request that is made. This client can be run from a separate application on a different server to truly test communication between the client and the REST service. The following is the code for the ClientController:
App::uses('HttpSocket', 'Network/Http'); class ClientController extends AppController { public $components = array('Security', 'RequestHandler'); public function index(){ } public function request_index(){ // remotely post the information to the server $link = "http://" . $_SERVER['HTTP_HOST'] . $this->webroot.'rest_phones.json'; $data = null; $httpSocket = new HttpSocket(); $response = $httpSocket->get($link, $data ); $this->set('response_code', $response->code); $this->set('response_body', $response->body); $this -> render('/Client/request_response'); } public function request_view($id){ // remotely post the information to the server $link = "http://" . $_SERVER['HTTP_HOST'] . $this->webroot.'rest_phones/'.$id.'.json'; $data = null; $httpSocket = new HttpSocket(); $response = $httpSocket->get($link, $data ); $this->set('response_code', $response->code); $this->set('response_body', $response->body); $this -> render('/Client/request_response'); } public function request_edit($id){ // remotely post the information to the server $link = "http://" . $_SERVER['HTTP_HOST'] . $this->webroot.'rest_phones/'.$id.'.json'; $data = null; $httpSocket = new HttpSocket(); $data['Phone']['name'] = 'Updated Phone Name'; $data['Phone']['manufacturer'] = 'Updated Phone Manufacturer'; $data['Phone']['name'] = 'Updated Phone Description'; $response = $httpSocket->put($link, $data ); $this->set('response_code', $response->code); $this->set('response_body', $response->body); $this -> render('/Client/request_response'); } public function request_add(){ // remotely post the information to the server $link = "http://" . $_SERVER['HTTP_HOST'] . $this->webroot.'rest_phones.json'; $data = null; $httpSocket = new HttpSocket(); $data['Phone']['name'] = 'New Phone'; $data['Phone']['manufacturer'] = 'New Phone Manufacturer'; $data['Phone']['name'] = 'New Phone Description'; $response = $httpSocket->post($link, $data ); $this->set('response_code', $response->code); $this->set('response_body', $response->body); $this -> render('/Client/request_response'); } public function request_delete($id){ // remotely post the information to the server $link = "http://" . $_SERVER['HTTP_HOST'] . $this->webroot.'rest_phones/'.$id.'.json'; $data = null; $httpSocket = new HttpSocket(); $response = $httpSocket->delete($link, $data ); $this->set('response_code', $response->code); $this->set('response_body', $response->body); $this -> render('/Client/request_response'); } }
And here is a screenshot of the index action for the CLientController.
Step 7: Remember to use a secure connection
A well-written RESTful application should only accept secure https connections!!! Although I have commented out the call to:
$this->Security->requireSecure();
In line 46 of AppController’s beforeFilter() function, you should never go to production with this pieces of code turned off. This line of code tells the RESTfull application to blackhole all requests that are not over a secure connection! This is the safest way to protect your application from man-in-the-middle attacks!
Step 8: Download it all and start testing
That concludes the tutorial. You can downloadthe entire tutorial in zip format here. It was written using CakePHP 2.4 and I also include the SQL file used to create the phones database. Fell free to play with the client and modify the REST service as you like. If you’ve got questions or comments, hit me up. I’ll help if I can.
Thanks for the great tutorial. I followed it and everything is great. But i was wondering if there is a way to use strings instead of numbers in the rest server? right now, i tried to change your code from using phone id to using phone name and i am getting errors. how can i use names instead of ids for rest? please help.
Hey Amit!
Thanks for pointing this out. I was supposed to mention this in the article but I forgot.. Cake allows you to define the type for the “id” parameter that you pass to REST controllers. By default, the type is numeric only but you can change it to alphanumeric. To do so, you simply modify your routes.php file where you define the RESTFulness of Cake. Simply define the format of your id parameter by using a regular expression. For example, below is a modified routing to accept numbers and letters:
With this, I would be able to pass an id that is the phone name instead of the actual phone id…
Hey Mifty,
how can i use email instead of ids. i have already use name but mail id is not working.because special character @ is converted in some other% character.
Or
How i can use security check in rest api like user authentication in rest api.
Hi Tanseer,
You can use email instead of id or name. The reason you are seeing the % character is because of the urlendocing that cakePHP automatically does. As safety precaution, all special characters are always urlencoded by CakePHP and many other PHP frameworks. You can read all about urlencoding at http://php.net/manual/en/function.urlencode.php
Special thanks for you. It’s very helpful for me
glad I could help đ
i have some problem..
Warning: include(Cake\bootstrap.php): failed to open stream: No such file or directory in C:\xampp\htdocs\app\webroot\index.php on line 90
Warning: include(): Failed opening ‘Cake\bootstrap.php’ for inclusion (include_path=’C:\xampp\htdocs\lib;.;C:\xampp\php\PEAR’) in C:\xampp\htdocs\app\webroot\index.php on line 90
Fatal error: CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your \cake core directory and your \vendors root directory. in C:\xampp\htdocs\app\webroot\index.php on line 99
can you help me please
Hey walas,
Baed on error output, it looks to be a problem with your Cake installation. You seem to be missing the file bootstrap.php. Did you simply copy my zip file without adding the core components of CakePHP? Please note that I keep my zip file small by not including all the core files and components of CakePHP…
Hey Mifty!
can you give one example on the same using security. coz i have to create a secure restful api.
Thanks a lot in advance!!!
Hi guddu,
I currently don’t have time to create this tutorial, but I will look into it for the future.
the add don’t work for me; when i try to add, i just have the json format at screen without the new element i want add. help
are you getting any kind of error? Its a bit hard to debug without specific information đ
I also got the same issue, but with 100% certain you forgot to add your entity in routes.php.
What would i do if wanted to push parameter on function add? the parameters would be name, manufacture, description. Chiefly on $link.
Hello Mifty! I thank you for this tuto! It was very helpfull.
But i have some trouble!
Can we share a session between 2 restcontrollers ?
ex : I create $_SESSION[‘user_id’] on a RestRequestConnect and use this value on RestMyProjects. I’ve tried but failed.
If it doesn’t work, I suppose i can use Auth, isn’t ? Thanks for giving me more lightening.
There is a way to set a session between client and server but it is a little bit more complicated than what is in this tutorial
thanks!
Awesome tutorial. It will add a SPARK if we add API key and secret concept in it.
Hi Mifty.
Great tutorial!
Small nit:
The line
Router::mapResources(‘phones’);
should instead read
Router::mapResources(‘rest_phones’);
Cheers
Awesome Tutorial… đ
Thanks for the help in understanding the RESTfulness in CakePHP.
Hi Mifty,
I have implemented your code and It’s working with new cake framework But with my website It’s save data in database but generate error below :
Response Code
200
Response Body
Fatal error: Uncaught exception ‘MissingViewException’ with message ‘View file “/var/www/meritnation.newnav/app/View/Themed/newNav/RestUsers/json/add.ctp” is missing.’ in /var/www/meritnation.newnav/lib/Cake/View/View.php on line 671 MissingViewException: View file “/var/www/meritnation.newnav/app/View/Themed/newNav/RestUsers/json/add.ctp” is missing. in /var/www/meritnation.newnav/lib/Cake/View/View.php on line 671 Call Stack: 0.0159 352708 1. {main}() /var/www/meritnation.newnav/app/webroot/index.php:0 0.0494 2128084 2. Dispatcher->dispatch() /var/www/meritnation.newnav/app/webroot/index.php:109 0.1081 4801532 3. Dispatcher->_invoke() /var/www/meritnation.newnav/lib/Cake/Routing/Dispatcher.php:89 0.5767 20394552 4. Controller->render() /var/www/meritnation.newnav/lib/Cake/Routing/Dispatcher.php:114 0.5896 20458640 5. View->render() /var/www/meritnation.newnav/lib/Cake/Controller/Controller.php:899 0.5997 22162028 6. View->_getViewFileName() /var/www/meritnation.newnav/lib/Cake/View/View.php:363
I have done everything according to your code.
Please let me know what is missing here.
if we send a request to client than xml response data is not display in correct formatting.while if we hit direct response method on ie browser than we getting respone in correct formetting.please resolve my issues.
Hi Tanseer,
This is a very basic tutorial. I simply created the tutorial to help others understand REST in CakePHP. For sepcial cases like the one you are experiencing, you will have to to debug it yourself.
you are a legend. thanks for helping me with cake rest
Glad I could help đ
Awesome tutorial. you really saved my life
Thanks for This tutorial…
Very Usefull..
Wow, incredible easy to read and follow. thanks mate. you saved my bacon.
Thanks a lot! This helped me a lot.
Even though I am still having problems with security component blackholing my actions, but I figure this out.
Thank you.
If you face ‘The request has been black-holed’ issue, kindly inspect your `unlockedActions ` and CakePHP version.
$this->Security->unlockedActions requires CakePHP version >= 2.3.
For lower versions, we can use:
$this->Security->csrfCheck = false;
$this->Security->validatePost = false;
Documentation: http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html
Hii mifty,
Its a great tutorial but can you provide me the same kind of tutorial for the facebook REST API integration in cakePHP. Its really urgent
Thank you
Hi Mifty,
Thats very nice . Verry verry tahnk u for ur grt tutorial….
It would be nice to get to know, what has actually to be done, to use a secure connection.
For this tutorial I am using a XAMPP environment and CakePHP 2.4.10. When I uncomment
$this->Security->requireSecure()
I get the black hole error message. Using https:// in the URL doesn’t fix it. So what does a secure connection require?
Greetings
Christian
Please show me how to calls methods (add, edit, view, delete) from android app (client)
Hey man, did you realize how to manage it with android? how to make a correct request?
I never had a chance to look into it đ
I’m doing by socket and I got the index, view, delete but I can not get to add and edit. I’ve tried everything (almost) but the data is coming null. Almost sure that’s something about the json. I expect come back with the answer ;D
@Mifty, tks for this tutorial ;D
so, the problem was that I was not catching the correct data. Instead of receive a json was receiving a object. I changed to:
“if ($this->User->save($this->request->input(‘json_decode’))) {….”
Thanks a lot it worked perfectly, now i got another question, is there a way that i can get that json data from a Jquery AJAX request?
I can’t get a right request which gives the data correctly. Please
great job, thanks for sharing your thoughts on that.
I also struggled today with implementing individual controller’s functions for both onscreen data viewing and API calls made by external websites.
Very good tutorial on how to implement RESTful API with CakePHP.