Creating a PHP REST API Using the Zend Framework


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:

  1. PHP version > 5
  2. 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.

Download .htaccess
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

Download 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.

Download index.php
<?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.

, , , , , , ,

  1. #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. #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. #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. #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. #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. #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. #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. #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. #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. #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. #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. #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. #13 by Chris Danielson on October 28th, 2009

    Björn :
    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

    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. #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. #15 by Chris Danielson on October 28th, 2009

    Excellent. That makes total sense in the Zend paradigm. Thanks for sharing!
    Regards,
    Chris

  16. #16 by Olivier Berger on December 4th, 2009

    What is the copyright / license on your code ?

  17. #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. #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. #19 by Andreas Kompanez on February 17th, 2010

    dangtruog :
    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?

    Nowhere actually, but shows nicely how to access your application.ini parameters.

  20. #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. #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. #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

  23. #23 by David on June 1st, 2013

    Chris – thanks for the quick tutorial. I’m actually glad I didn’t got for Zend because it looks a lot messier than some other frameworks. Thought it all depends on what features you need or will need for your project in future.

    Zend is good but if one wants to build a simple RESTful API from ground up without any framework dependency then it’s probably much easier to grab something lighter cutting the learning curve and get the job done quicker. I’ve looked into Slim and Laravel and they seem to cover pretty much most of my needs i.e. building APIs + AJAX front end.

    For those evaluating frameworks herewith a quick list: http://davss.com/tech/php-rest-api-frameworks/

    There was no point of listing all detailed features as they change too frequent so it’s best to go to websites to read fresh descriptions.

(will not be published)