Supporting PUT & DELETE in the Zend Framework
Creating a RESTful (Representational State Transfer) Web service is not simply about serving read-only content through HTTP GET requests. It’s about using the full range of HTTP’s constrained interface to allow clients to consume or create resources within your service. Take a look at CouchDB, for example. Its initial releases look very promising, and the server accepts GET, POST, PUT, and DELETE requests to manipulate resources in the system. I can’t wait until the project implements authentication and authorization features; then, it will look much more attractive for real-world use.
But I digress…
I’ve never been too happy with the Zend Framework’s RPC-based approach to creating RESTful Web services with Zend_Rest_Server, even though I have seen some good discussion about using routes and Zend_Rest_Server to create a resource-oriented architecture. Rather than get too in-depth about this issue, I’ll just point to this thread and save my full thoughts on Zend_Rest_Server for another day.
Suffice it to say, Zend_Rest_Server is not focused on resources but, instead, what you can do with those resources (procedures, methods, verbs) and also assumes you’re only ever going to provide an XML-based, read-only REST service. With REST, this is not the case, and, with the publication of the Atom Publishing Protocol (a protocol that follows the REST architectural style) as RFC 5023, now is the time more than ever to grasp the read-write capabilities of the RESTful Web.
But I digress (again)…
I’ve recently been wrapped up in an effort to design and implement a RESTful API using the Atom Protocol for a project at work. We are using the Zend Framework as the underlying framework for the project, so, in order to follow the Atom Protocol, I needed to support the HTTP methods PUT and DELETE. Apache can handle GET and POST easily because the request itself tells Apache the resource to use when processing the request. With PUT or DELETE, the resource identified by the request may not even exist, so Apache needs you to specify a script to process the request. To do this, I added the following lines to my virtual host configuration:
# PUT and DELETE support Script PUT /index.php Script DELETE /index.php
Now, all PUT and DELETE requests are handled by the Zend Framework bootstrap script and the dispatcher handles them in the same way it handles GET and POST requests.
To further support other HTTP methods and the REST architectural style, I’ve proposed the addition of the following methods on the Zend_Controller_Request_Http class:
isGet()- Was the request made by GET?isPut()- Was the request made by PUT?isDelete()- Was the request made by DELETE?isHead()- Was the request made by HEAD?isOptions()- Was the request made by OPTIONS?getEntityBody()- Return the raw entity body of the request, if present
It’s a very simple addition, but feel free to comment on my patch and offer any improvements or additions.
See also PUT method support in the PHP manual.
13 Comments
Nice Ben, I like this implementation. I agree HTTP protocol is not limited to just GET and POST methods.
I saw your patch and I could help you if needed.
Very interesting! I've been experimenting with this myself and such additions as you're suggesting is really necessary to make it work.
When I tested I didn't write anything special in my Apache-configuration and it worked anyway. Are you sure you need those additions?
I had to implement other http methods a bunch of times, including DELETE/PUT and with webdav I also had to deal with PROPPATCH PROPFIND etc..
However, there was no need for special apache configuration.. I remember a long while back with Apache 1 and PHP4.something this was the case.. but thats a couple of years ago
Just a quick one - I'm watching you Ben Ramsey ;). Hope this all works out so my next web API will be in the Zend Framework and I just need a few PUTs to update resources. For those not following the conceptual links - Atom as an XML format is concerned with collections of entries, which is why it's instantly reusable (with maybe a few custom namespaces for customisation) for representing a collection of any resource in a RESTful api.
Whether the ZF classes do I don't know (honestly haven't needed it to yet). Obviously they don't support everything needed right now for PUT/DELETE - so best of luck with the patches. You should look, for curiousity, at how Rails' new RESTful controllers work with a bit of fudging over PUT/DELETE.
bq. When I tested I didn’t write anything special in my Apache-configuration and it worked anyway. Are you sure you need those additions?
I could be wrong, but I think the Apache set-up is necessary because if I try to
PUTa resource that doesn't exist, I'll get a404 Not Foundresponse. That's not exactly what I want to happen. I want the Zend Framework to handle that request and create/modify the resource. Now, if I only usePUTto update resources, then perhaps I don't need to configure Apache in this way. Likewise, withDELETE, I may not need to configure Apache.It doesn't hurt to do the configuration, though, to make things explicit. :-D
bq. You should look, for curiousity, at how Rails’ new RESTful controllers work with a bit of fudging over PUT/DELETE.
Pádraic, I'll assume you're talking about the use of the
_methodfield to include the "real" HTTP method even though the actual one used might be aPOST. Another alternative is the use of the X-HTTP-Method-Override request header to specify the method to use.The service I'm developing won't support these alternatives, though I should look into them since I've heard that some firewalls actually block
PUTandDELETE(for whatever reason).No this is not true.. as long as you have some kind of rewrite rule in effect..
PUT'ing on index.php/anything will work as well
Also, I don't really understand the direct need for this patch, I get getEntityBody (well almost, I think when you're dealing with bigger requests you should work with a file handle, not a string)
But the rest of the patch just makes an switch or if statement slightly prettier?
e.g.:
if ($something->isPut()) vs.
if ($something->getMethod()=='PUT')
not a big deal?
bq. But the rest of the patch just makes an switch or if statement slightly prettier?
That's pretty much the reasoning. Plus, there's already an
isPost(), so why not have the others? It also means you're not dealing with strings in conditionals all throughout your application that you could accidentally misspell, making it very hard to debug.Overall, I think the maintainability aspect of
$this->getRequest()->isPut()trumps the use of$this->getRequest()->getMethod() == 'PUT'.bq. I think when you’re dealing with bigger requests you should work with a file handle
Not a bad suggestion. Though, if you're parsing a large XML document, I don't see where processing it sequentially would be feasible with any of the parsers in PHP. AFAIK, all the XML parsers in PHP need to read in the entire XML string first, in which case
file_get_contents()works fine here.I've not used "XMLReader":http://ww.php.net/xmlreader much yet. Will it parse a stream sequentially? If so, then perhaps it's your best bet here, but that's only if you're reading in XML data. If you're reading in JSON, you still need to read in the entire string to decode it with "
json_decode()":http://us.php.net/json_decode.What other formats would be able to take advantage of sequential reads with
fopen()/fread()?hm well good point about your last comment.. I guess I'm dealing with the same shit with WebDAV, but instead I need to anticipate large files because of the nature of the protocol. Atom doesn't have that as much..
Great to hear someone is working on this problem. Was really disappointed when I looked at the documentation for the zend_rest_server and saw now support for a full RESTful web service. Hope your proposal is accepted.
If you have an script as part of the path you don't have to change your apache config, thus PATH_INFO is the trick. The request method doesn't matter, if the path is something like /yourfrontcontroller.php/any/path it's always your front controller that gets called.
Getting the input as file handle/stream (=php://input) could be an advantage in PUT, when you don't want to read the data. You could than use stream_copy_to_stream() to save it in a file. For parsing XML you also still have the old SAX parser.
I was looking at the same kind of problem, but I think, the REST-RPC-Solution isn't good anyways. RESTful-Controllers like in Ruby seem to be the best solution.
RESTful Controllers First Try
Excellent. This is a real positive contribution to the Zend Framework & it's great to see your patch is now formally included in v151.
Thanks, Ben!