Monday, November 9. 2009Building RESTful Services with Zend FrameworkAs 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:
The standard URL structure used is as follows:
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:
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 artMany 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:
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! 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 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.
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/ How easy is it to configure the "verbs", i.e. view/show -> GET (action) or destroy/purge/erase -> DELETE (action)?
Never mind, I missunderstood the url structure (assumed it was similar to RoR's map.resources).
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? 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.
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.
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
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... 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. 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. 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 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/ 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: 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. 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) 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 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.
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.
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.
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. 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. 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. Add Comment
|
CalendarQuicksearchLinks
ArchivesCategoriesSyndicate This BlogShow tagged entries |






In previous articles, I've explored building service endpoints and RESTful services with Zend Framework. With RPC-style services, you get to cheat: the protocol dictates the content type (XML-RPC uses XML, JSON-RPC uses JSON, SOA
Tracked: Mar 04, 15:30