ZF: Creating RESTful Applications

Over the last few days I have been trying to use the Zend_Rest_Controller along with the Zend_Rest_Route to make a RESTful application for an API that I am working on. It is very easy to get things setup with these components and I will cover that now. You might be asking yourself why do I have to use both components, why is it not rolled into one nice component. The answer is really basic and if you look at the source code for the Zend_Rest_Controller you will see that it is solely an abstract class that defines the methods required for use with the Zend_Rest_Route. When your controller extends from the Zend_Rest_Controller it just forces you to implement the indexAction(), getAction(), postAction(), putAction() and deleteAction() methods. Technically you do not have to extend from Zend_Rest_Controller so long as you implement these methods in all of your RESTful controllers. It is good design to extend the class though as it will throw exceptions for developers who forget to add one of these methods. Without the methods the Zend_Rest_Route cannot route the request to the matching RESTful methods.

In order to get started you have to add a method to your Bootstrap file. This method can be named anything you like however it has to start with _init.

PHP:
  1. protected function _initRoutes()
  2.     {
  3.         $this->bootstrap('frontController');
  4.         $frontController = Zend_Controller_Front::getInstance();
  5.        
  6.         $restRoute = new Zend_Rest_Route($frontController);
  7.  
  8.         $frontController->getRouter()->addRoute('default', $restRoute);
  9.     }

I choose to call mine _initRoutes() because this is where I would put all of my routes. The first thing you need to do is make sure that the frontController has been setup and this is done with the call to $this->bootstrap('frontController'). Once that is completed you get the instance for your FrontController and create the new route object. You get the Router object from the FrontController and simply add your route. If you look at how I added the route you will see that I named the route 'default'. This is so that the Rest Route will become the default route for my application. This allows your entire application to be restful. You might be asking why that matters, couldn't you have more than one route in your application? The short answer is yes however if you add any other routes then you are going against the principals of rest by adding non restful routes to your application.

Once this portion is completed all you have to do is create a controller that extends from Zend_Rest_Controller as such:

PHP:
  1. class MyController extends Zend_Rest_Controller
  2. {
  3.     public function init()
  4.     {
  5.         /* Initialize action controller here */
  6.     }
  7.  
  8.     public function indexAction($id = null)
  9.     {
  10.         $this->getResponse()
  11.                 ->setHttpResponseCode(200)
  12.                 ->appendBody('indexAction has been called.');
  13.     }
  14.  
  15.     public function getAction()
  16.     {
  17.          $this->getResponse()
  18.                 ->setHttpResponseCode(200)
  19.                 ->appendBody('getAction has been called');
  20.     }
  21.    
  22.     public function postAction()
  23.     {
  24.         $this->getResponse()
  25.                 ->setHttpResponseCode(200)
  26.                 ->appendBody('postAction has been called');
  27.     }
  28.    
  29.     public function putAction()
  30.     {
  31.         $this->getResponse()
  32.                 ->setHttpResponseCode(200)
  33.                 ->appendBody('putAction has been called');
  34.     }
  35.    
  36.     public function deleteAction()
  37.     {
  38.         $this->getResponse()
  39.                 ->setHttpResponseCode(200)
  40.                 ->appendBody('deleteAction has been called');
  41.     }
  42. }

With this in place your application is now RESTful. There is only one problem that I am facing. We are building a RESTful web API at work and I have been trying to put the API version in the URL. Currently we are using modules so I would expect to be able to do something like the following: /2.0/products/11 and have it call the getAction and return the product details for the product with the ID of 11. However in ZF you cannot name modules with the 2.0 syntax. Therefore I created the modules named m2m0 which stands for major 2 minor 0. I needed to find a way to actually route this properly. The Zend_Rest_Route would not route it properly by default because in the internals it checks the dispatcher to see if you have all the valid modules/controllers/actions defined. It fails when it looks for a module named 2.0 and falls back to use the 'default' module. I have tried using a FrontController plugin like you can with all of the other routes. The normal ZF routes do not check with the dispatcher to ensure that the modules/controllers/actions that are routed to are proper and thats where the issue lies. After trying everything I could think I ended up extending the Zend_Rest_Route and copying the match() method into the new file which overrides the match() method in Zend_Rest_Route. I was able to add my 4 lines of code to do the mapping for the module names but it also led to duplication of around 100 lines of code from the base Zend_Rest_Route class. This is not the best method but until I hear more from the ZF team it is the only work-around I have found. I have created a ticket on the Issue Tracker to see what the internal developers think about this issue.



Development, Frameworks, Internet, PHP, Rants, Raves, Tips & Tricks, Tutorials.

5 Comments

Hirvine

Cool, I just made the /2.0/controller/action solution Saturday, but without the Zend_Rest_Controller and Zend_Rest_Controller for various reasons ...

To make /2.0/controller/action you can change the default route.

Zend_Controller_Front::getInstance()->getRouter()->addRoute('default' ,
new Your_Extend_Rest_Route(
':api_version/:controller/:action',
array(
'module' => 'default',
'api_version' => '1.0',
'controller' => 'index',
'action' => 'index'
)
)
);

in the class `Your_Extend_Rest_Route`
you do

public function match($path, $partial = false)
{ // First match like the parent use to
// and then set the controller paths
// This will also change your view paths
// Since view paths uses by default the modulespath
$result = parent::match($path, $partial);
// TODO does the controller directory exists ?
// Otherwise force to an error page
// Though you get the idea right?
Zend_Controller_Front::getInstance()->setControllerDirectory(array('default' => APPLICATION_PATH.'/controllers/'.$result['api_version']));

return $result;
}

Obvious there would be a different or even better way. But I find this solution more appropriate than even a controller named by api version.

what do you think about it?


Sudheer

Nice.

I've written a series on creating RESTful applications using the Zend Framework on the Tech Chorus blog.


luke

Nice post Joseph. I've got ZF-9372 in my inbox and will get to it as soon as I can.


[...] ZF: Creating RESTful Applications | Joseph Crawford [...]


Have your say...

CommentLuv Enabled
Technology Blogs Add to Technorati Favorites Page Rank Tool NYPHP Users Group View Joseph Crawford's profile on LinkedIn

   

SEO Consultant SEO services