Exposing Service APIs via Zend Framework

The hubbub surrounding "Web 2.0" is around sharing data. In the early iterations, the focus was on "mashups" -- consuming existing public APIs in order to mix and match data in unique ways. Now, more often than not, I'm hearing more about exposing services for others to consume. Zend Framework makes this latter trivially easy via its various server classes.

All Zend Framework server classes follow PHP's SoapServer API. In a nutshell, you can basically do the following with any server class:

$server = new Zend_XmlRpc_Server();
echo $server->handle();

Each server protocol we support in this way -- SOAP, XML-RPC, JSON-RPC, and AMF -- has its own little nuances, but the basics follow the above pattern.

Where should you do this, however? Many developers want to stick this in their MVC application directly, in order to have pretty URLs. However, the framework team typically recommends against this. When serving APIs, you want responses to return as quickly as possible, and as the servers basically encapsulate the Front Controller and MVC patterns in their design, there's no good reason to duplicate processes and add processing overhead.

Additionally, there's often a need to version your APIs. As you add new features or need to change method signatures, you'll need to introduce a new version of the API for developers to consume.

One recommendation to solve each problem is to move your server endpoints into your public directory structure, and then utilize your web server's URL rewriting capabilities. As an example, you could organize your endpoints as follows:

|-- api
|   |-- v1
|   |   |-- xmlrpc.php
|   |   |-- soap.php
|   |   |-- jsonrpc.php

You might then configure your URL rewriting as follows:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^api/v1/xmlrpc api/v1/xmlrpc.php [L]
RewriteRule ^api/v1/soap api/v1/soap.php [L]
RewriteRule ^api/v1/jsonrpc api/v1/jsonrpc.php [L]
RewriteRule ^.*$ index.php [NC,L]

This allows you to move the service scripts to other locations if necessary, as well as to have each have explicit dependencies to insulate them from changes elsewhere in the codebase.

As a standard best practice, you do not want code duplication. Code duplication becomes quite common when taking the above strategy, as each endpoint script will often have common logic for bootstrapping the application. One way you can avoid this is to leverage Zend_Application. You can do this in one of two ways: (1) instantiate Zend_Application using the same configuration as your MVC application, and selectively bootstrap necessary resources; or (2) extend your MVC application's bootstrap class, and override the run() method.

In the first case, you might do the following in your server endpoint scripts:

// Initialize application
require_once 'Zend/Application.php';
$app = new Zend_Application(
    APPLICATION_PATH .  '/configs/application.ini'

// Selectively bootstrap resources:

// Instantiate server, etc.
$server = new Zend_XmlRpc_Server();

In the second case, you would subclass your application bootstrap class, and override the run() method. Such an extending class could look like the following:

class XmlRpc_Bootstrap extends Bootstrap
    public function run()
        $server = new Zend_XmlRpc_Server();
        echo $server->handle();

You would also need to modify your application bootstrapping slightly to notify it of your new bootstrap class:

$app = new Zend_Application(
        'bootstrap' => array(
            'class' => 'XmlRpc_Bootstrap',
            'path'  => 'path/to/Bootstrap.php',
        'config' => APPLICATION_PATH . '/configs/application.ini',

So, the takeaway is: Zend Framework makes exposing web services easy, and the addition of Zend_Application makes it trivially easy to re-use application configuration in order to expose your servers via discrete, unique endpoints in your application. What are you waiting for?

blog comments powered by Disqus