Blog

RESTful webservices using CakePHP

Webservices are really important. There are very few cases that web applications does not really need to publish their functionality through API and even in that cases having an API is a positive and nice feature to have. Not too much blah blah, I will dive into how webservice can be done in CakePHP with less effort and using the core functionality. This note is not finished and will be updated for some coming days.

There are many ways to do this in CakePHP the ones which are compatible with the older versions of CakePHP up to the ones which use only features from the latest version. In my case there was no problem to use latest version of CakePHP and my goal was to do as less as possible and use core features as much as possible.

I wanted to send JSON encoded data and receive JSON back. All these should go to the same URL that html call for resources are sent and the only way to differ API calls from the html requests of browser are the additional http headers sent with request.

I found some plugins, and also some routing tricks but all of them was not proper for me. I depended on the features of CakePHP 2.4.3 and used the core features.

Server setup

I used CakePHP 2.4.3 to acomplish this task. Followings are different aspects of doing a RESTful API:

  • Mapping HTTP methods on one resource to specific actions in controller
  • Sending and receiving data either JSON or XML encoded (I will do it JSON here)

Resource mapping

One part of being RESTful is right mapping to resources, REST has its own definitions but they are not fix, so you are not bound to use them always. However if you prefer to use RESTful like paths, then you have to include this in your routes.php:

Router::mapResources('users');

how this will map different HTTP methods to different actions of your controller is listed on this page.

However at some point you may endup using paths that are not fully RESTful, thats not a problem at all.

Receive JSON

as first step to accept, parse and put JSON data into request->data when Content-Type: application/json header is set, you have to load RequestHandler:

public $components = array('RequestHandler');

More details can be found here.

Send back JSON

This was the most confusing part for me. Basically CakePHP does recognize the proper data structure to be sent back using extensions. For this to work you have to put following in routes.php:

Router::parseExtensions();

Now if a client wants to receive JSON from /users resource, has to make call to /users.json. This was working but I wanted to do it using only headers and the URLs should not be altered. To make it work this way I had to explicitly list json in parse extensions:

Router::parseExtensions('json');

and now making a call to /users accompanied with a Accept: application/json will result in the same result as making call to /user.json.

My Client

I have a client which uses HTML code and jQuery and one plugin was responsible for reading and serializing data into JSON and send it to the same URL that form would have been submitted otherwise. For this case it was the best if I could reuse the HTML code generated by CakePHP’s FormHelper. This is an extreme case and is not always the case that client is also based on HTML.

At the end this was not possible to use the code generated by FormHelper because the JSON deserializer of CakePHP does not comply with the URL Encoded deserializer standards. To make it a bit clearer, consider following input which is generated by FormHelper: html <input name="data["User"]["email"]" />

Generally sending back this piece of data as URL encoded data, what normal POST will do, will map that piece of data to $this->request->data[User][email]. When JSON encode that data into this object:

data: {
	User: {
		email: "…"
	}
}

And send it to the server, the CakePHP’s deserializer will map it to $this->request->data["data"]["User"]["email"]. For this reason I had to change my HTML into something like following:

<input name="User[email]" />

Additionally the correct headers should be sent in jQuery’s request, my code to post data to API looked like following:

$.ajax({
    type: "POST",
    url: server + "/users",
    data: JSON.stringify($(form).serializeObject()),
    headers: {
        Accept : "application/json; charset=utf-8",
        "Content-Type": "application/json; charset=utf-8"
    },
    success: function(data) { },
    failure: function(errMsg) { }
});

Conclusion

All in all, that was easy to setup, but because the whole setup I had was a bit more complicated than one may face, it was a bit trial and error to find the right way. For that reason I wrote this guide.

comments powered by Disqus