Programming and Technology
RSS icon Home icon
  • Implement a Rest API with the Zend Framework

    Posted on May 6th, 2009 brandon 17 comments

    Edit: Recent developments in the 1.9 version of the Zend Framework has caused much of this information to become obsolete. Please consider Zend_Rest_Server deprecated and use Zend_Rest_Controller instead. I will write an updated article once I have experimented with it. Thanks to the other developers who brought this to my attention.

    As a developer and I admit that I judge the utility of a website by the availability of an API so that I can leverage it in my own projects. Most popular web services offer an API of some sort such as Flickr, Blip.TV, Youtube, Twitter, Facebook, etc. The most popular way to implement an api that is accessible to all styles of developers is to implement it using REST. In a nutshell, REST is a method of utilizing the HTTP protocol to send and retrieve information. REST leverages the various request types that HTTP supports (GET,POST,PUT,DELETE) while web browsers typically only use GET and POST to fetch web pages and submit form information. I won’t go into the intricacies of REST other than to note that the primary idea behind REST is that web services are presented in an object oriented fashion. REST urls typically comprise of a reference to an object or service of some type and an action to perform on the object. Parameters that are required to perform the requested action are provided as post variables in the header rather than as part of the url.

    An example of a typical REST url for retrieving a photo would be:

    http://typical.webservice.com/rest/photo/1

    When accessed using the GET method of the HTTP protocol, the assumption is that you want to retrieve this photo. The response might be the image itself, or more typically it will be a JSON or XML encoded response containing a url to the image stored on a CDN. Alternately, if this same url is accessed using a DELETE request, the photo would instead be deleted and an encoded status report would be returned.

    Chances are if you’re reading this article you already know all of this and just want to know how to implement this using the Zend Framework. This example utilizes controllers in an MVC style application. The first thing to understand is that REST is not implemented in the controller itself, instead the controller simply serves as a broker for various classes we have for handling different types of REST requests. Most of the boilerplate functionality is handled by an instance of  Zend_Rest_Server so all we have to do is implement our service.

    In keeping with the REST philosiphy you will want to visualize how your web service will be accessed in terms of objects and their associated actions. It might help to draw up some use cases, but in this case lets keep going with our photo example. Let’s assume we want to allow users to upload photos, retrieve them, delete them, etc. Using this example, the first place to start naturally would be a class that represents Photos. Before that however, make sure you have set up your MVC application how you like it. Make sure you have written an appropriate class to represent your photo models complete with your persistence layer implementation. If you have no idea what i’m talking about, take a look at my application skeleton examples.  For those who are ready, we can implement our REST handlers now.

    I like to start with a base class that contains functionality that will need to be shared across all the REST handlers. Here is a simple base class that we can extend.

    1.  
    2. class Rest_Server {
    3.    protected function authenticate($userid, $apikey){
    4.      /* Authentication implementation goes here*/
    5.    
    6.     if (!$authenticated)
    7.       throw new Exception(‘Access Denied’);
    8.    }
    9. }
    10.  

    This class implements one method right: authenticate(). Most web services require some form of authentication and/or api key to prevent abuse so this application shouldn’t be any different. This method is obviously just a stub as your authentication method can be whatever you prefer.

    Next lets implement a default REST handler that responds to generic requests.

    1.  
    2. class Rest_Server_Default extends Rest_Server {
    3.  public function getVersion($userid, $apikey){
    4.     $this->authenticate($userid, $apikey);
    5.  
    6.     return Rest_Response::Generate(array(‘version’ => 1.0));
    7.   }
    8. }
    9.  

    This class implements getVersion() which responds with the current version of the web service software. I call it the default handler because it will serve as a fallback handler when no context is specified in the REST url. The name of the method is important because that is the name of the action used in the url (Example: http://webservice.com/rest/getVersion).  This method takes two arguments ($userid and $apikey). You will notice that all REST methods will minimally require these arguments. The purpose of these arguments is so a developer implementing your API can provide their credentials. The names of these arguments map directly to the name of your request parameters (http://webservice.com/rest/getVersion?clientid=1&apikey=1234567890). These parameters can be provided as post variables as well.

    All REST methods will always return an encoded response using a custom response generator Rest_Response.

    1.  
    2. class Rest_Response {
    3.  
    4.   protected $_xml = null;
    5.   protected $_status = null;
    6.  
    7.   public function __construct($pStatus=true, array $pResponse=array()){
    8.     $this->_xml = simplexml_load_string(‘<?xml version="1.0" encoding="utf-8"?><response></response>’);
    9.     $this->appendStatus($pStatus);
    10.  
    11.     if (count($pResponse) > 0)
    12.       $this->appendResponse($this->_xml, $pResponse);    
    13.   }
    14.  
    15.   public function appendStatus($pStatus=true){
    16.     if (is_null($this->_status)){
    17.       if ($pStatus === true)
    18.         $this->_xml->addChild(’status’, ’success’);
    19.       else if ($pStatus === false)
    20.         $this->_xml->addChild(’status’, ‘fail’);
    21.       else
    22.         throw new Exception(‘Invalid response status’);
    23.     } else
    24.       throw Exception(‘Response already has status’);    
    25.   }
    26.  
    27.   public function appendResponse($pXml, array $pResponse){
    28.     foreach ($pResponse as $key => $val){
    29.       if (is_array($val)){
    30.         $child = $pXml->addChild($key);
    31.         $this->appendResponse($child, $val);
    32.       } else
    33.         $pXml->addChild($key, $val);
    34.     }
    35.   }
    36.  
    37.   public function __get($pAtt){
    38.     switch ($pAtt){
    39.     case ‘xml’:
    40.       return $this->_xml;
    41.     }
    42.   }
    43.  
    44.   public static function Generate(array $pResponse, $pStatus=true){
    45.     $response = new Rest_Response($pStatus, $pResponse);
    46.     return $response->xml;
    47.   }
    48. }
    49.  

    The purpose of this class is to generate responses in a uniform and consistent structure. Each response regardless of the context or action should use the same structure which makes a developer implementing your API want to pull out his hair less than if each response required a completely different parser.  This class provides a static method Generate() which takes an associative array as its argument which it converts into xml. Optionally, Generate() takes a status argument which indicates whether the request Succeeded or failed. This is helpful in case the response parser needs to respond differently in the event of a failure.

    So let’s make another REST handler that handles requests for photos.

    1.  
    2. class Rest_Server_Photo extends Rest_Server {
    3.  
    4.   public function upload($userid, $apikey){
    5.     /* Handle file upload */
    6.  
    7.    /* Save data to db */
    8.  
    9.    return Rest_Response::Generate(array(‘photoid’ => $newphotoid), true);
    10.   }
    11.  
    12.   public function get($userid, $apikey, $photoid){
    13.  
    14.     /* Retrieve record from db */
    15.  
    16.    return Rest_Response::Generate(array(‘url’ => $photo->url), true);
    17.   }
    18.  
    19.   public function delete($userid, $apikey, $photoid){
    20.  
    21.     /* Delete record from db */
    22.  
    23.    return Rest_Response::Generate(array(), true);
    24.   }
    25. }
    26.  

    This handler is a little more sophisticated. It responds to requests to upload photos, retrieve them and delete them. Just like the default handler, each method requires credentials and some take additional arguments. The upload method receives the file and generates a database record for the photo which has an id associated with it which can be returned in the response. The get() method takes an id for an argument that it uses to retrieve a database record for a photo and then returns the information associated with it. Similarly, delete() takes an idea and deletes the associated photo from the database and returns a status report.

    So now we have handlers for our REST operations but we haven’t yet seen where the controller fits into all of this. As I said earlier, the controller doesn’t only acts as a broker for the REST requests. In this application we have a single controller RestController with a single method restAction()

    1.  
    2. class RestController extends Zend_Controller_Action
    3. {
    4.   //Rest API action
    5.   public function restAction(){
    6.     $this->_helper->Layout->disableLayout();
    7.     $this->_helper->ViewRenderer->setNoRender();
    8.  
    9.     $params = $this->_request->getParams();
    10.     unset($params[‘controller’]);
    11.     unset($params[‘action’]);
    12.     unset($params[‘module’]);
    13.  
    14.     $server_prefix = ‘Rest_Server_’;
    15.  
    16.     if (array_key_exists(‘context’, $params)){
    17.       switch ($params[‘context’]){
    18.       case ‘photo’:
    19.         $server_class = $server_prefix . ‘Photo’;
    20.         break;
    21.       default:
    22.         throw new Exception(‘Invalid rest context’);
    23.       }
    24.     } else
    25.       $server_class = $server_prefix . ‘Default’;
    26.    
    27.     $server = new Rest_Handler();
    28.     $server->setClass($server_class);
    29.     $server->returnResponse(true);
    30.  
    31.     $responseXML = $server->handle($params);
    32.  
    33.     if (!array_key_exists(‘format’, $params)){
    34.       if ($this->_request->isXmlHttpRequest())
    35.         $format = ‘json’;
    36.       else
    37.         $format = ‘xml’;
    38.     } else
    39.       $format = $params[‘format’];
    40.  
    41.     switch ($format){
    42.     case ‘xml’:
    43.       $this->_response->setHeader(‘Content-Type’, ‘text/plain’)->setBody($responseXML);
    44.       break;
    45.     case ‘json’:
    46.       $this->_response->setHeader(‘Content-Type’, ‘application/json’)->setBody(Zend_Json::fromXML($responseXML));
    47.       break;
    48.     }
    49.   }
    50. }
    51.  

    restAction() looks pretty gnarly at first glance. Believe me when I was figuring out how to do this it started of pretty simple but rapidly evolved to handle various use cases as most code does. I’ll go over it step by step.

    Obviously in order to satisfy all of the requirements of both the controller and our REST handlers we need some workable routes which directs REST requests to this action.

    1.  
    2. $routes = array(
    3.                       ‘default_rest’ => new Zend_Controller_Router_Route(‘rest/:method’, array(‘controller’ => ‘rest’, ‘action’ => ‘rest’)),
    4.                       ‘default_rest_media’ => new Zend_Controller_Router_Route(‘rest/:context/:method’, array(‘controller’ => ‘rest’, ‘action’ => ‘rest’)),
    5.                      );
    6. Zend_Controller_Front::getInstance()->getRouter->addRoutes($routes);
    7.  

    The first route allows us to make requests in the default context. This allows us to call http://webservice.com/rest/getVersion

    The next route allows us to specify a context. For example: http://webservice.com/rest/photo/get?id=1

    So first things first we need to disable the view and layout plugins since all we are interested in getting back from this action is either pure XML or JSON
    . If we don’t disable the layout or view engines they will go looking for non-existant templates, or worse find some and wrap out XML or JSON in HTML.

    1.  
    2.     $this->_helper->Layout->disableLayout();
    3.     $this->_helper->ViewRenderer->setNoRender();
    4.  

    Next we need to grab the request parameters. We strain out the parameters that get automatically appended by the controller infrastructure because they are not needed by the REST handlers.

    1.  
    2.     $params = $this->_request->getParams();
    3.     unset($params[‘controller’]);
    4.     unset($params[‘action’]);
    5.     unset($params[‘module’]);
    6.  

    Next we determine if there is a context in the request. This allows us to determine which REST handler we need to instantiate and invoke.

    1.  
    2.     $server_prefix = ‘Rest_Server_’;
    3.  
    4.     if (array_key_exists(‘context’, $params)){
    5.       switch ($params[‘context’]){
    6.       case ‘photo’:
    7.         $server_class = $server_prefix . ‘Photo’;
    8.         break;
    9.       default:
    10.         throw new Exception(‘Invalid rest context’);
    11.       }
    12.     } else
    13.       $server_class = $server_prefix . ‘Default’;
    14.  

    Now we instantiate a Zend_Rest_Server object and assign our custom handler. I’m sure this can be streamlined to be a little more dynamic.

    1.  
    2.     $server = new Rest_Handler();
    3.     $server->setClass($server_class);
    4.     $server->returnResponse(true);
    5.  

    And now we let the handler do its thing. If we fail to set returnResponse(), the REST server will ignore everything after this line and send its response back to the requester directly. By setting this to true, we ensure the handler will return its response to the controller so we can do some housekeeping.

    1.  
    2.     $responseXML = $server->handle($params);
    3.  

    In order to be truly flexible with the needs of other developers we really should offer a variety of response formats. Some developers prefer JSON while others need XML. It’s a good idea to accept a format parameter that allows the developer to specify the preferred format or assign a default format otherwise. Once we know what format to use, we need to set the Content-Type header and then send the response back to the requester.

    There’s not much more to it than that, although as a footnote, I subclassed the default Zend_Rest_Server object because I wanted to to return custom XML in the event of an uncaught exception in the REST handlers.

    1.  
    2. class Rest_Handler extends Zend_Rest_Server {
    3.  
    4.   public function fault($exception = null, $code = null){
    5.     $xml = simplexml_load_string(‘<?xml version="1.0" encoding="utf-8"?><response></response>’);
    6.  
    7.     $xml->addChild(’status’, ‘fail’);
    8.  
    9.     if ($exception instanceof Exception)
    10.       $xml->addChild(‘error’, $exception->getMessage());
    11.     else
    12.       $xml->addChild(‘error’, ‘Unknown error’);
    13.  
    14.     if (is_null($code) || (404 != $code))
    15.       {
    16.         $this->_headers[] = ‘HTTP/1.0 400 Bad Request’;
    17.       } else {
    18.       $this->_headers[] = ‘HTTP/1.0 404 File Not Found’;
    19.     }
    20.     return $xml;
    21.   }
    22. }
    23.  

    It’s very important that you are diligent about catching exceptions and handling them properly otherwise the response can be ruined with garbage such as error messages outside of the XML or JSON, or worse, they could be blank. To use this simply instantiate this class instead of Zend_Rest_Server. So with this knowledge go forth and create. The web needs more clean and robust web services.

    • Share/Bookmark
     

    17 responses to “Implement a Rest API with the Zend Framework”

    1. [...] Implement the Rest API with the Zend Framework [...]

    2. [...] Implement a Rest API with the Zend Framework [...]

    3. [...] Implement a Rest API with a Zend Framework [...]

    4. why are you not using context switcher? That seems like an obvious thing to use with this

    5. I haven’t had an opportunity to implement that but I agree that is a better way to go for altering the response format.

    6. [...] Implement a Rest API with the Zend Framework [...]

    7. [...] implement a rest api with the zend framework [...]

    8. [...] Implement a Rest API with the Zend Framework [...]

    9. Isn’t Zend_Rest_Server being deprecated for favor of Zend_Rest_Controller and Zend_Rest_Route?

      I see a lot of conflicting information and examples.

    10. It’s possible that is the case now. It was not the case when I first implemented a REST server with the zend framework. Care to provide links which make this case?

    11. Hi Brandon -

      If you look at the comments here (May 05), it looks like that is the case

      http://framework.zend.com/issues/browse/ZF-6527

      I’m learning that things move quick in zendland!

      Perhaps Zend_Rest_Server isn’t being replaced, just augmented, with Zend_Rest_Contoller, etc.

      The docs don’t say anything about Zend_Rest_Server going away, and I think Zend_Rest_Controller is new as of 1.9.2

    12. Sweet. Thanks. I’ll have to give that a shot and write a new article.

    13. Damn, this looks usefull :D thanks brandon. But just a quick question. When I build the website, should I make the website first then the API or just make them together?

      Looking forward to the next article :)

    14. Sebbe, I don’t think it really matters. You may want some scaffolding in place to handle user accounts if you require an api key though.

    15. Matthew, mentioned on the mailing list that deprecating Zend_Rest_Server is being considered.

      You should really use Zend_Rest_Controller to create RESTful server.

      I recently wrote an article on it – http://techchorus.net/create-restful-applications-using-zend-framework

    16. [...] Implement a Rest API with the Zend Framework [...]

    17. [...] Rest API mit dem Zend Framework 1.9 [...]

    Leave a reply

Get Adobe Flash playerPlugin by wpburn.com wordpress themes