I don’t know about you, but I spent hours pouring over the Zend documentation and searching the Internet for some sort of understanding or example regarding how to do REST the correct way in PHP. I found a handful of one-offs, where everyone was writing their own core REST engine from scratch. I figure why write all the code to handle this, if you already have the Zend Framework in your code base. I believe that the ideal solution will effectively use the the Zend_Rest_Controller and ContextSwitch objects as well as offering JSON and XML formatting options. My goal here is simple. I want to give you the building blocks to aid in writing an easily maintainable REST API in PHP using the Zend Framework.
This code example will support REST data in the format of XML and JSON.
Prerequisites:
- PHP version > 5
- Zend Framework 1.9.2 (at the minimum) The generic download page can found here.
Download ZendRestExample Source. You will want to download this example in order to effectively run and better understand the project. Make sure to rename the 1.htaccess file to .htaccess (if interested in using it). As well, take note that there are other files not listed below that are required to run this project. These are contained within the zip’d source code. Finally, the Zend framework is not zip’d into the source code. You’ll need to download this and install it on your own.
Basic Directory Structure:
/ /.htaccess /index.php /applications/ /applications/configs/ /applications/controllers/ /applications/forms/ /applications/layouts/ /applications/views/ /library/ (optional where the Zend folder containing the framework should reside)
URLS used in this example:
http://yourSite/version/format/xml http://yourSite/version/format/json //with other parameters you can do: http://yourSite/version/format/xml/id/1234/param2/6789
One of the tricky things with REST is that the URLs need to be formatted properly. In Apache, all you need is the mod_rewrite module enabled and a .htaccess in the root of your current web project.
SetEnv APPLICATION_ENV development RewriteEngine On #RewriteBase /~myUserName/myDirectory RewriteBase / RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ index.php [NC,L] |
Now that we have adjusted the .htaccess, let’s look into the application/configs/application.ini as well as the important Bootstrap.php.
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_PATH "/Bootstrap.php" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" bootstrap.class = "Bootstrap" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 |
The core kick-start of this whole REST application is contained in the applications/Bootstrap.php
<?php class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _initAutoload() { $autoloader = new Zend_Application_Module_Autoloader(array( 'namespace' => 'Default_', 'basePath' => dirname(__FILE__), )); return $autoloader; } protected function _initRestRoute() { $this->bootstrap('Request'); $front = $this->getResource('FrontController'); $restRoute = new Zend_Rest_Route($front, array(), array( 'default' => array('version') )); $front->getRouter()->addRoute('rest', $restRoute); } protected function _initRequest() { $this->bootstrap('FrontController'); $front = $this->getResource('FrontController'); $request = $front->getRequest(); if (null === $front->getRequest()) { $request = new Zend_Controller_Request_Http(); $front->setRequest($request); } return $request; } } |
Now we need to wire up the ability for web requests to move through this engine. We do this in our /index.php file.
<?php // Define path to application directory defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/application')); // Define application environment defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production')); // Ensure library/ is on include_path /* If you do not have the Zend library already included in your PHP installation... 1. Create a directory named library and place the Zend Framework into it. NOTE: In my instance, I have the Zend library residing under /usr/share/php/ Because of this, the following lines are commented. */ /*set_include_path(implode(PATH_SEPARATOR, array( realpath(APPLICATION_PATH . '/library'), get_include_path(), ))); */ /** Zend_Application */ require_once 'Zend/Application.php'; // Create application, bootstrap, and run $application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini' ); $application->bootstrap()->run(); |
Well now that the core is in place, all we need is to integrate our controllers. This is where our business logic will reside.
<?php class VersionController extends Zend_Rest_Controller { public function init() { $bootstrap = $this->getInvokeArg('bootstrap'); $options = $bootstrap->getOption('resources'); $contextSwitch = $this->_helper->getHelper('contextSwitch'); $contextSwitch->addActionContext('index', array('xml','json'))->initContext(); //$this->_helper->viewRenderer->setNeverRender(); $this->view->success = "true"; $this->view->version = "1.0"; } /** * The index action handles index/list requests; it should respond with a * list of the requested resources. */ public function indexAction() { //if you want to have access to a particular paramater use the helper function as follows: //print $this->_helper->getParam('abc'); //To test with this use: http://myURL/format/xml/abc/1002 } public function listAction() { $this->_forward('index'); } public function getAction() { $this->_forward('index'); } public function newAction() { $this->_forward('index'); } public function postAction() { $this->_forward('index'); } public function editAction() { $this->_forward('index'); } public function putAction() { $this->_forward('index'); } public function deleteAction() { $this->_forward('index'); } } |
Now that we’ve done this, the URL for format of JSON just works as-is thanks to the Zend ContextSwitch engine. No other changes are needed. If you are interested in having the output in XML format, then you’ll need to make a few additions. Now that we’ve taken care of the Controller, we’re going to have to add in the views to make the XML work with the ContextSwitch. So under /applications/views/scripts/version/ you’ll need a file: index.xml.phtml
<?php $doc = new DOMDocument(); $doc->formatOutput = true; $root_element = $doc->createElement("response"); $doc->appendChild($root_element); $statusElement = $doc->createElement("success"); $statusElement->appendChild($doc->createTextNode($this->success)); $root_element->appendChild($statusElement); $versionElement = $doc->createElement("version"); $versionElement->appendChild($doc->createTextNode($this->version)); $root_element->appendChild($versionElement); print $doc->saveXML(); ?> |
And that is all there is to it. We’ve now made a basic index/list REST API for listing the version of your product. This is an overly simple example of how to integrate quickly the Zend REST engine.
I'm a software developer focused on all facets of enterprise solutions and technologies. Currently, I'm enjoying developing iPhone applications at night while spending much of my day working on Java, .Net and database implementations.

#1 by Vince on September 18th, 2009
Hi Chris — I’m starting to read on Zend_Rest_Controller and would like to know if an extended class like the one you have above can determine the HTTP method being used and automatically route to the correct action … for instance POST gets forwarded to the postAction routine, GET to getAction, PUT to putAction, DELETE to deleteAction.
Perhaps the _initRequest() routine in the bootstrap above has something to do with it?
Thanks,
Vince
Zend FTW ! ;0)
#2 by Chris Danielson on September 18th, 2009
Vince,
That may work, but what I have noticed thus far is that the ContextSwitch will automatically handle this type of request for you. But what is still baffling me is the fact that I have to aid the ContextSwitch by specifying additional parameters in the URL.
For instance, the “post” method which should inherently call the postAction() method does not trigger this method without the following aid:
(HTTP POST)
http://myUrl/api/version (Pretend there is a postAction waiting here. This DOES NOT WORK)
versus
(HTTP POST)
http://myUrl/api/version/post (Pretend there is a postAction waiting here. This DOES WORK)
Strange. Let me know if you find that the _initRequest causes the engine to work appropriately.
Regards,
Chris
#3 by Vince on September 21st, 2009
Hi again — I’ve found another nice post on Zend_Rest_Controller:
http://avnetlabs.com/zend-framework/restful-controllers-with-zend-framework
I need however to take a different direction with my current project at work, but will come back to analyzing the _initRestRoute and _initRequest routines which I believe to be key here.
Looking forward to continuing our dialog.
Regards,
Vince
#4 by Marc Grabanski on September 21st, 2009
I keep an open mind for learning all frameworks and languages; however, your article here has convinced me to stay far away from Zend Framework. RESTful APIs come out of the box with CakePHP, even with the ability to just toss in parseExtensions to get json, xml, or html back automatically.
#5 by Chris Danielson on September 22nd, 2009
Marc,
Great point regarding CakePHP. After looking over the CakePHP library and reading a few reviews, I believe this is another great framework. It seems to me that no matter what framework you choose, it’s a “pick your poison” situation in the end. Each framework has it’s hiccups. The biggest one here with Zend, is the URI layout and the lack of an automated XML engine. For my solution, which I’ll post later on, I wrote a getXML method in the abstract base class that extends Zend_Rest_Controller. This way I can generically simply call the
$this->getXML(…);
to auto-generate the XML goodness. Zend takes care of the rest. If anything, the fact that Zend didn’t produce an automated XML solution means that one has a lot more flexibility immediately. That’s a glass half-full perspective.
Cheers,
Chris
#6 by Peter Rawsthorne on October 5th, 2009
I’ve got the .htaccess file exactly the same as described.
I’ve got the httpd.conf file containing the RewriteEngine set to on. Is there any other configuration issue i should be aware? I still get a 403 when the URL is processed.
Thanks in advance for your time.
#7 by Peter Rawsthorne on October 5th, 2009
Oops… what i should have written was;
I’ve got the httpd.conf file containing the AllowOverride set to All.
#8 by Chris Danielson on October 5th, 2009
Peter,
Thank you for the comments.
I’ve had the situation with a 403, but it was due to the fact that I did not have the mod_rewrite module installed. The easiest way to see what shared objects you have installed is to run from the command line:
apache2 -M
or
apache2 -l
If this computer is using httpd instead, just replace the apache2 above with the name “httpd”. You should see a rewrite_module listed in one of those commands above. BTW, -M is for the shared libs and -l (that is an lowercase ‘L’) is for compiled in modules.
The 403 forbidden error could also mean that you are having a security/access issue with the file system. Here are the permissions on my system that is functioning.
drwxr-xr-x 8 cd cd 4096 Oct 4 22:36 application
-rw-r–r– 1 cd cd 1016 Oct 2 03:47 index.php
Atop that, I’m wondering if you can look into your /var/log/apache/ directory (if it exists) and see if any logs within that location can shine any further light on the situation.
Regards,
Chris
#9 by Chris Danielson on October 13th, 2009
Marc,
Just wanted to reply that I finally got around to blogging my XML recursive methods for dynamically handling the generation of the XML. I agree this functionality should be automated into the Zend Framework.
Thanks again for bringing this point up.
Regards,
Chris
#10 by Björn on October 28th, 2009
Hi,
please correct me if I’m wrong but AFAIK Zend_Rest_Route will not match URLs other than http://yourSite/version and http://yourSite/version/:id. In your example all requests that contain the parameters “format” or “id” will bypass the REST route and be routed to VersionController and its indexAction by the default route. I am currently trying to create a REST service in ZF that implements the context switch with no luck and found your example.
Best regards,
Björn
#11 by Chris Danielson on October 28th, 2009
Björn,
You may be correct that the REST route is bypassed. I haven’t looked at an exact stack trace, nor have I crawled the code base to see if I’m not getting the ContextSwitch put into place. What I have discovered in the short term studying I did with Zend REST Framework was that the URL issue you are running across regarding the “index” action always getting called was fixed by doing the following.
http:/yourSite/version/get
or
http:/yourSite/version/post/:id
or
http:/yourSite/version/put
That seems to force the engine into a particular conditional allowing for the “other” actions to be called.
Regards,
Chris
#12 by Björn on October 28th, 2009
Hi Chris,
thanks for your reply. I think it makes perfect sense that appending ‘get’ or ‘post’ to the URL works, because it would also match the default route and not the REST route. The ‘index’ action then is called of course because of the _forward statements.
But never mind, I found out what I did wrong in my app and it works now (simply wrong usage of the contextSwitch ie.).
Best regards,
Björn
#13 by Chris Danielson on October 28th, 2009
No problem. Thanks for commenting here on this information. What did you do to get the ContextSwitch functioning predictably? Is there a particular call that I wasn’t making?
Kind Regards,
Chris
#14 by Björn on October 28th, 2009
Well, it’s quite easy. According to the documentation the URLs that the REST routes matches to are only http:/yourSite/version and http:/yourSite/version/:id and how the rest controller is invoked depends on the request method (GET, POST, PUT etc.). To get the ContextSwitch functioning you have to append the parameter ‘format’ as a query string like http:/yourSite/version?format=xml or http:/yourSite/version/:id?format=json. That’s it. I also added a frontcontroller plugin that detects if the request is a XmlHttpRequest (for ajax stuff) and inserts the format parameter into the request object.
Best regards,
Björn
#15 by Chris Danielson on October 28th, 2009
Excellent. That makes total sense in the Zend paradigm. Thanks for sharing!
Regards,
Chris
#16 by Olivier Berger on December 4th, 2009
What is the copyright / license on your code ?
#17 by Chris Danielson on December 4th, 2009
My code is available for you to freely use and distribute. No limitations, just user beware, know what you are implementing and don’t blame me if you blow up your stack.
Regards,
Chris
#18 by dangtruog on February 1st, 2010
Hi Chris Danielson,
I’m newbie ZF, I have a question.
Why we have theses code below:
[code]
$bootstrap = $this->getInvokeArg('bootstrap');
$options = $bootstrap->getOption('resources');
[/code]
Where is used it on your code?
#19 by Andreas Kompanez on February 17th, 2010
Nowhere actually, but shows nicely how to access your application.ini parameters.
#20 by shashi on February 5th, 2011
Hi, i have tried this one and it worked awesome.
Now what i want to try is that:
How can i send a parameter to an xml file using REST, and how to get the response from database in xml format.
#21 by shashi on February 7th, 2011
Hi, i have tried this script, and it works perfectly !
However I wanted to generate this xml response through a curl script….
I mean, users will get this xml response when they are authenticated in my site, and then they send request to the REST controller url, through a php curl script, for getting the xml output. How to achieve this?
#22 by Christophe on July 24th, 2011
Hello,
I’m new to Zend and installed it in order to write some REST services.
I took your sample project, placed it into the Apache root folder for the Apache installed by Zend Server
C:\Program Files\Zend\Apache2\htdocs\ZendRestExample
and imported it into Zend Studio from there
But i’m not able to get the JSON or XML result from your sample
Below, I put the result for the different urls.
Could you please tell me what I’m doing wrong please ?
Thanks in advance for your help.
Best regards,
Christophe
curl.exe http://localhost:8080/ZendRestExample/version
404 Not Found
Not Found
The requested URL /index.php was not found on this server.
=> KO
curl.exe http://localhost:8080/ZendRestExample/public/version
Success: trueVersion: 1.0
=> OK but the url is /public/version so different from your above article
curl.exe http://localhost:8080/ZendRestExample/public/version/format/json
Zend Framework Default Application
An error occurred
Page not found
=> KO