A complete RESTful service and client in CakePHP tutorial

in CakePHP/Tutorials & Samples

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:)

rest-service-screenshot

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:

  1. 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”.
  2. 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.

rest-service-client-screenshot
rest-service-index-screenshot

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.

Tags:

Mifty Yusuf is a Montreal-based software developer who enjoys playing with new web technologies as well as comic books and illustrations. He beleives that, no matter what the question is, the answer is always Batman!

72 Comments

  1. 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:

      Router::mapResources(array('rest_phones'), array('id'=>'[0-9a-zA-Z]+'));
      Router::parseExtensions();
      

      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.

  2. 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…

  3. 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!!!

  4. 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

  5. What would i do if wanted to push parameter on function add? the parameters would be name, manufacture, description. Chiefly on $link.

  6. 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.

  7. Hi Mifty.
    Great tutorial!
    Small nit:
    The line
    Router::mapResources(‘phones’);
    should instead read
    Router::mapResources(‘rest_phones’);
    Cheers

  8. 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.

  9. 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.

  10. 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.

  11. 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

  12. 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

        • 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’))) {….”

  13. 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

  14. 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.

Leave a Reply

Your email address will not be published.

*

Latest from CakePHP

Go to Top