REST and Resource Handling with CakePHP

by Nate Abele (2008-05-14)
 

In the previous article in this series, we learned how the CakePHP routing system handles URLs and interacts with your application in order to dispatch requests to the proper controller action. In the first article of the series, we saw how the Router's features could be combined with other classes in the framework (the , for example) to enable things like unifying the handling of different resource types.We're going to take these concepts further and add a new one: REST.

In the previous article in this series, we learned how the CakePHP routing system handles URLs and interacts with your application in order to dispatch requests to the proper controller action. In the first article of the series, we saw how the Router's features could be combined with other classes in the framework (the RequestHandler, for example) to enable things like unifying the handling of different resource types.

We're going to take these concepts further and add a new one: REST. In the course of this series so far, we've only been discussing how to use the Router to examine and act on different parts of a URL. However, the URL is not the only piece of information we're concerned with when a client is making an HTTP request. Along with a URL that defines what an HTTP client is requesting, the client also sends headers, which help you determine all sorts of useful details, like what kind of information the client is sending, and what it expects back.

We are able to use these headers in order to implicitly handle various aspects of a client's request that might otherwise require extra user input or programmatic configuration from people using our site or service. As a refresher, here a few of the more useful ones:

  • Accept: The range of content types this client accepts. Not always reliable, so we have to fall back on an explicit declaration, like file extension.
  • Accept-Charset: The character sets that the client accepts. Hopefully Unicode is among these.
  • Accept-Language: The client's locale code, which you can use to determine how to translate any text content.
  • Accept-Encoding: Lets you know whether the client accepts compressed data streams, allowing you to transfer data to the client faster.
  • Content-Type: When a client sends data on a POST or PUT request, it will send this header. For form submissions, this value is typically application/x-www-form-urlencoded. If the client submits XML data, however, the value may be text/xml or application/xml, or content-type of a more specific XML dialect, like application/atom+xml.
  • Request Method: The HTTP verb used when sending a request, either GET, POST, PUT or DELETE (plus some other lesser ones). Mapping these HTTP verbs to controller actions is the foundation of our REST architecture.
  • So the question is, how do we take headers into account when routing our requests? Fortunately, the Router is able to speak HTTP header using a special syntax, like the following:
  1. Router::connect(
  2.   "/:controller/:id",
  3.   array("action" => "edit", "[method]" => "PUT"),
  4.   array("id" => "[0-9]+")
  5. );

By wrapping array keys in the second parameter with square brackets, it is possible to create routes which respond based on certain HTTP headers and server environment variables. When writing routes, you can use any server variable that starts with HTTP_, as well as several custom ones: type (maps to CONTENT_TYPE), method (maps to REQUEST_METHOD), and server (maps to SERVER_NAME). Any HTTP server variables are used lowercase and without the HTTP_ prefix; i.e. HTTP_ACCEPT_LANGUAGE becomes accept_language.

There are many possible ways in which to leverage this, including automatically localizing content, detect supported encodings and automatically compressing data streams, and determining how to handle incoming POST and PUT data.

For purposes of this tutorial, the principal header of interest is REQUEST_METHOD, as it enables the Router to map HTTP verbs to controller actions. Now, you could specify one route (like the above) for each HTTP verb, but the Router provides you with a handy wrapper method to wire them all up at once.

  1. // config/routes.php
  2. Router::mapResources('posts');

This will map the following requests to the corresponding controller actions, as follows:

  • GET /posts: PostsController::index()
  • GET /posts/5: PostsController::view(5)
  • POST /posts: PostsController::add()
  • PUT /posts/5: PostsController::edit(5)
  • DELETE /posts/5: PostsController::delete(5)
  • POST /posts/5: PostsController::edit(5)
  • The last one is not actually a part of REST convention, but was added to improve compatibility with web clients, and makes request handling significantly easier. Also, when evaluating the REQUEST_METHOD header, Cake takes into account the X_HTTP_METHOD_OVERRIDE header, as well as a POST variable called _method. Either of these can be used to enable proper REST support in a web browser (the POST variable, being more explicit, takes precedence).

Using Resources

Now that REST handling has been mapped, we can move on to using it to manage domain objects as full-blown HTTP resources. Referring back to previous articles in the series, add the following:

  1. // config/routes.php
  2. Router::parseExtensions();

Again, this tells the Router to recognize and parse file extensions in URLs.

  1. // controller/posts_controller.php
  2. var $components = array('RequestHandler');

Also from previous examples, this is used in the controller to handle template switching, and setting the proper response headers based on the custom extension. Now that this is set up, we can begin serving resources in multiple representations. Consider the following example:

  1. // controllers/posts_controller.php
  2.  
  3. class PostsController extends AppController {
  4.  
  5.   var $components = array('RequestHandler');
  6.  
  7.   function index() {
  8.     $posts = $this->Post->find('all');
  9.     $this->set(compact('posts'));
  10.   }
  11.  
  12.   function view($id) {
  13.     $post = $this->Post->findById($id);
  14.     $this->set(compact('post'));
  15.   }
  16.  
  17.   function edit($id) {
  18.     $this->Post->id = $id;
  19.     if ($this->Post->save($this->data)) {
  20.       $message = 'Saved';
  21.     } else {
  22.       $message = 'Error';
  23.     }
  24.   }
  25. }

Using this setup, templates for content types like XML can easily be added as follows:

  1. // views/posts/xml/index.ctp
  2.  
  3. <posts>
  4.   <?php echo $xml->serialize($posts); ?>
  5. </posts>

As you can see, simply creating templates in a subdirectory of the views folder, named according to the file extension, it is now possible to serve XML content for the index() view. You'll note that the view is using a helper called $xml, which wasn't specified in the controller.

When serving custom content types, the RequestHandler checks to see if there are any helpers corresponding to that type. Since the CakePHP core includes XmlHelper, is is automatically added to the list of helpers loaded in the view. The record output is then passed to it, and the helper renders it as a set of XML tags. Nothing to it. In fact, the view for the view() action is almost the same:

  1. // views/posts/xml/view.ctp
  2.  
  3. <?php echo $xml->serialize($post); ?>

Except in this case the <posts /> wrapper tags are not needed, since there is already only one root node in the document.

When it comes to the edit action, things get more complicated, since there is also input to be handled. Since an XML interface is being provided, it's only natural for clients to want to send XML to do updates. This is where another bit of RequestHandler magic comes it. After examining the content-type of the incoming POST or PUT request, if it is an XML type, then the input is taken and passed to an instance of Cake's Xml object, which is assigned to the $data property of the controller. Since Cake Models already know how to speak Xml object, handling XML and POST data in parallel is seamless: no changes are required to the controller or model code.

By following the same model as the RequestHandler, you can write your own components to automatically transform input content to a format that Cake can handle, thereby abstracting that logic away from the rest of your application.

Using these techniques, you're well on your way to fully REST-API-enabling your applications on CakePHP.

Nate Abele has been a core developer of the CakePHP framework for over two years. Widely regarded as the Johnny Cash of the PHP community, his hobbies are playing guitar, wearing black, and snowboarding. While not enjoying one of these or other hobbies, you can find him writing about himself in the third person. At the time of this writing, Nate resides in New York City near the office of his employer, OmnTI Inc., and is about to enjoy an extremely hot beverage.
File under: art  cake  cakephp  homepage  rest  web services 
 

Comments

Re: REST and Resource Handling with CakePHP by mbavio (2008-05-16 09:19:07 (America/Toronto))
Brilliant.
Visit the forum