LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Monday, November 9. 2009

Building RESTful Services with Zend Framework

As a followup to my previous post, I now turn to RESTful web services. I originally encountered the term when attending php|tropics in 2005, where George Schlossnaggle likened it to simple GET and POST requests. Since then, the architectural style -- and developer understanding of the architectural style -- has improved a bit, and a more solid definition can be made.

At its heart, REST simply dictates that a given resource have a unique address, and that you interact with that resource using HTTP verbs. The standard verbs utilized are:

  • GET: retrieve a list of resources, or, if an identifier is present, view a single resource
  • POST: create a new resource with the data provided in the POST
  • PUT: update an existing resource as specified by an identifier, using the PUT data
  • DELETE: delete an existing resource as specified by an identifier

The standard URL structure used is as follows:

  • "/resource" - GET (list) and POST operations
  • "/resource/{identifier}" - GET (view), PUT, and DELETE operations

What the REST paradigm provides you is a simple, standard way to structure your CRUD (Create-Read-Update-Delete) applications. Due to the large number of REST clients available, it also means that if you follow the rules, you get a ton of interoperability with those clients.

As of Zend Framework 1.9.0, it's trivially easy to create RESTful routes for your MVC application, as well as to handle the various REST actions via action controllers.

Zend_Rest_Route allows you to define RESTful controllers at several levels:

  • You can make it the default route, meaning that unless you have additional routes, all controllers will be considered REST controllers.
  • You can specify modules that contain RESTful controllers.
  • You can specify specific controllers per module that are RESTful

As examples:


$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();

// Specifying all controllers as RESTful:
$restRoute = new Zend_Rest_Route($front);
$router->addRoute('default', $restRoute);

// Specifying the "api" module only as RESTful:
$restRoute = new Zend_Rest_Route($front, array(), array(
    'api',
));
$router->addRoute('rest', $restRoute);

// Specifying the "api" module as RESTful, and the "task" controller of the
// "backlog" module as RESTful:
$restRoute = new Zend_Rest_Route($front, array(), array(
    'api',
    'backlog' => array('task'),
));
$router->addRoute('rest', $restRoute);
 

To define a RESTful action controller, you can either extend Zend_Rest_Controller, or simply define the following methods in a standard controller extending Zend_Controller_Action (you'll need to define them regardless):


// Or extend Zend_Rest_Controller
class RestController extends Zend_Controller_Action
{
    // Handle GET and return a list of resources
    public function indexAction() {}

    // Handle GET and return a specific resource item
    public function getAction() {}

    // Handle POST requests to create a new resource item
    public function postAction() {}

    // Handle PUT requests to update a specific resource item
    public function putAction() {}

    // Handle DELETE requests to delete a specific item
    public function deleteAction() {}
}
 

For those methods that operate on individual resources (getAction(), putAction(), and deleteAction()), you can test for the identifier using the following:


if (!$id = $this->_getParam('id', false)) {
    // report error, redirect, etc.
}
 

Responding is an art

Many developers are either unaware of or ignore the part of the specification that dictates what the response should look like.

For instance, in classic REST, after performing a POST to create a new item, you should do the following:

  • Set the HTTP response code to 201, indicating "Created"
  • Set the Location header to point to the canonical URI for the newly created item: "/team/31"
  • Provide a representation of the newly created item

Note that there's no redirect, which flies in the face of standard web development (where GET-POST-Redirect is the typical format). This is a common "gotcha" moment.

Similarly, with PUT requests, you simply indicate an HTTP 200 status when successful, and show a representation of the updated item. DELETE requests should return an HTTP 204 status (indicating success - no content), with no body content.

Note: when building RESTful HTML applications, you may want to still do GET-POST-Redirect to prevent caching issues. The above applies to RESTful web services, which typically use XML or JSON for transactions, and have smart clients for interacting with the service.

I'll be writing another article soon showing some tips and tricks for interacting with HTTP headers, both from the request and for the response, as it's a subject lengthy enough for a post of its own. In the meantime, start playing with Zend_Rest_Route and standardizing on it for your CRUD operations!

Posted by Matthew Weier O'Phinney in PHP at 09:00 | Comments (25) | Trackbacks (0)
Defined tags for this entry: php, rest, zend framework
Related entries by tags:
Creating Re-Usable Zend_Application Resource Plugins
Quick Start to Zend_Application_Bootstrap
Real-time ZF Monitoring via Zend Server
Exposing Service APIs via Zend Framework
Speaking at ZendCon 2009

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

That is a brilliant. I'm glad to see something coming up, especially the fact that the API looks so much like FRAPI makes me feel even better :-)

I think the next thing you should cover is how to retrieve put parameters and maybe even attempt to start a discussion on the different school of thoughts about POST vs PUT (Especially in the PHP world).

Good article! Great to see Zend_Rest_Route :-)
#1 David Coallier (Link) on 2009-11-09 09:35 (Reply)
I'll be doing some follow-up posts on these subjects soon. BTW, Zend_Rest_Route allows for usage of POST for PUT operations already; basically, you simply append a "_method=PUT " pair to your query string. With most ajax libraries already supporting PUT, however, it's not too big of a deal except when developing HTML REST apps.
#1.1 Matthew Weier O'Phinney (Link) on 2009-11-09 09:57 (Reply)
The comment I started writing grew too long, so I posted a response to this POST vs. PUT comment here:
http://benramsey.com/archives/post-vs-put/
#1.2 Ben Ramsey (Link) on 2009-11-09 11:12 (Reply)
How easy is it to configure the "verbs", i.e. view/show -> GET (action) or destroy/purge/erase -> DELETE (action)?
#2 Andris on 2009-11-09 09:39 (Reply)
Never mind, I missunderstood the url structure (assumed it was similar to RoR's map.resources).
#2.1 Andris on 2009-11-09 09:55 (Reply)
Great post. The question is, how to use the same controller logic of the Zend_Rest_Controller within a non-REST app.
For instance, let's assume twitter was written with ZF. They would probably use the Zend_Rest_controller and Route for their API. However, what would they use for their website (which obviously use the same API logic)? Would they write an entire new application that simply fire REST request? Isn't that overload?
#3 Shahar (Link) on 2009-11-09 09:48 (Reply)
You can mix and match routes with controllers, even REST controllers. In fact, Zend_Rest_Route even lets you define additional mappings for a given controller (mapping URL extensions to actions, such as /resource/foo => fooAction). You can also define additional routes that map to existing REST controller actions. Effectively, you can deploy your API and your web application together in the same controllers.
#3.1 Matthew Weier O'Phinney (Link) on 2009-11-09 10:01 (Reply)
IMO, if you use the same controller for RESTful as well as regular requests, the controller quickly becomes a mess of AjaxContent, ContentSwitch, JSON, etc helpers. Personally, I don't like to push too many things for one controller action.
#3.2 Sudheer (Link) on 2009-11-09 10:43 (Reply)
Hey, nice tutorial! I've got it on my list to get a look at this at some point but I must say that seeing this it strikes me just how much this looks like the solution I came up with when I recently implemented a restful service with unit tests in ZF 1.8 :-) So I'm pretty sure this is going to be an easy solution to work with, having the routing will make life easier, thanks Matthew!
#4 Lorna Mitchell (Link) on 2009-11-09 11:00 (Reply)
Thanks for the post (and the previous). I am thinking about trying to play with the REST approach for a CMS where all the CRUDs would internally use REST api.

Would you consider this as a good approach?

And also, what would you consider a good approach regarding the REST authorization?

Thanks...
#5 UJ on 2009-11-09 13:12 (Reply)
Thanks for this post. Ive been playing with it for a while, works great for simple urls's like /articles, /articles/:id and such, byt can it be used for more complex url's? For example /articles/:id/:article_field (title), or say filtering articles /articles/filter/:criteria.
Rest routes doesn't see to behave like normal non rest routes.
#6 Peter on 2009-11-09 17:16 (Reply)
Peter,

I'm in the process of proposing more enhancements to Zend_Rest, but for now you can do something like:

/articles/index/:filter1/:value1/:filter2/:value2

Everything after index/ is parsed as regular name/value params like all other ZF routing. In addition, you can also always add query params:

/articles/?filter1=val1&filter2=val2

Which would still be RESTful since the resource you're requesting is the result of an algorithm - i.e., filtering.
#6.1 luke (Link) on 2009-11-10 17:43 (Reply)
MWO,

Thanks for writing the guide on this. I'm going to propose some more REST-helping enhancements and classes for ZF soon. E.g.,

http://framework.zend.com/wiki/display/ZFPROP/Zend_Rest_Controller_DbTable
#7 luke (Link) on 2009-11-10 17:46 (Reply)
Do you have any guidance to offer on returning JSON? Use contextSwitch? Disable the view render and just return json encoded data from Zend_Json?

I think contextSwitch's automatic JSON serialization needs to be disabled at least for the index get since it will return a JSON object and not an array.

For what it's worth, dojo 1.3 and later has a data store explorer that can trivially be set up with Dojo's JsonRestStore to exercise your REST server.

http://www.sitepen.com/blog/2009/01/14/store-explorer/
#8 Rob T. on 2009-11-10 17:54 (Reply)
I use ContextSwitch like so:

$this->_contextSwitch = $this->_helper->getHelper('contextSwitch');
$this->_contextSwitch->setAutoJsonSerialization(false);
$this->_contextSwitch->addActionContexts(array('get' => array('json', 'xml'), 'index' => array('json')));

get.json.phtml:
#8.1 luke (Link) on 2009-11-10 22:33 (Reply)
I plan to post some information on contextual awareness in RESTful architectures in the next week or so. I went over many of the ideas in my "Architecting Ajax Applications in ZF" tutorial at ZendCon if you want to get a jump start (slides are at http://slideshare.net/weierophinney).

I do disable auto JSON serialization in favor of using view scripts; my thought on the matter is that the various response headers should be done in the view scripts, and that often there are slight differences between what is displayed between JSON, XML, and HTML -- moving that logic to view scripts allows me to keep the controllers clean and generic.
#8.2 Matthew Weier O'Phinney (Link) on 2009-11-11 08:58 (Reply)
Also, HATEOAS is one of the more important concepts in RESTful architecture, and the view scripts (i.e., representations) are the medium for transmitting hypertext. So I always try to pay special attention to the view scripts and not just throw data out there in the easiest way possible.

Though the built-in json features of ZF make even view scripts really easy:

echo $this->json($this->resources->toArray());

(where $this->resources is a Zend_Db_Table_Rowset)
#8.2.1 luke (Link) on 2009-11-11 10:38 (Reply)
Hi,

I think that's a good practice to let view scripts handle response and data representation but what about the controllers and a "normal" context ?

I mean, when we are into a "normal" context, we have two specific actions (new and edit) to show forms. In that case, we must have a session context because on validation failure post/put actions redirect to new/edit actions to show form with user data. So the controllers can't be so generic because they have to check isXmlHttpRequest() to know if they have to put data and errors into the session context.

if ($this->_request->isXmlHttpRequest()) {
// 400 (Bad Request) - validation failure
$this->_response->setHttpResponseCode(400);
$this->view->errors = $form->getMessages();
}
else {
$this->_context->errors = $form->getMessages();
$this->_context->data = $form->getValues();

return $this->_redirector->gotoUrl('/myresource/new');
}

I don't know if i'm very clear 'cause of my english :-)
#8.2.2 Benjamin Dulau on 2009-11-19 08:53 (Reply)
Actually, the Rest route has some provisions for this, and allows you to provide some additional actions that it can route, as well as provide the HTTP verbs to which they will respond. With this in mind, you can create /resource/new and /resource/edit endpoints as needed.
#8.2.2.1 Matthew Weier O'Phinney (Link) on 2009-11-19 09:04 (Reply)
Yeah ok for that. It was easy to add new and edit actions but i wonder if to cover all cases Zend rest controller shouldn't automate usage of a session context to maintain user data between post/put actions and redirection to new/edit actions after data validation when not in an Ajax context.

For now i have implemented these features into an abstract rest controller with preDispatch and postDispatch methods. But it would be great if it was native into Zend rest solutions, making controllers more clean :-)
#8.2.2.1.1 Benjamin Dulau on 2009-11-19 09:20 (Reply)
I'm going to assume you mean cookies for user data between redirects and not session values, since we're talking about REST here. ;-)
#8.2.2.1.1.1 luke (Link) on 2009-11-19 09:47 (Reply)
Any state retention flies in the face of REST as it impairs addressability of resources and adds complexity. In the event that statefulness is needed, it would ideally be contained entirely on the appropriate end of the conversation, leaving the transfers lean. I'm lost as to why you would think cookies would be better than other alternatives.
#8.2.2.1.1.1.1 Matt Whipple (Link) on 2010-02-04 12:30 (Reply)
Hum, i begin to wonder if i'm not out of the subject.

Maybe i didn't reply on the good message, I was reacting on a that remark :

"Effectively, you can deploy your API and your web application together in the same controllers"

And the fact that in many cases controllers have to do two different treatment depending if request is xmlHttp or not. Because if API and web application logic are handled by the same controllers, controllers can't be so generic.
#9 Benjamin Dulau on 2009-11-19 10:07 (Reply)
Using ContextSwitch, it's actually not only possible, but trivially easy to accomplish, particularly if you push the response object into the view. This allows you to keep the logic in the controller the same, but then to perform redirects if the context demands, or return output, etc.

I'll be blogging more on this topic in the coming weeks.
#9.1 Matthew Weier O'Phinney (Link) on 2009-11-19 10:45 (Reply)
In designing the new Zend_Rest classes, I stuck to the principle that the "web application" is simply an HTML+Javascript client of the API itself.

MWO'P nails it by using ContextSwitch features to customize any logic between ajax or "plain" requests.
#9.2 luke (Link) on 2009-11-20 00:26 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back February '10 Forward
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

February 2010
January 2010
December 2009
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net