LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Friday, October 23. 2009

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();
$server->setClass('My_Awesome_Api');
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:

public
|-- 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_ENV,
    APPLICATION_PATH .  '/configs/application.ini'
);

// Selectively bootstrap resources:
$app->bootstrap('db');

// 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();
        $server->setClass('My_Awesome_Api');
        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(
    APPLICATION_ENV,
    array(
        'bootstrap' => array(
            'class' => 'XmlRpc_Bootstrap',
            'path'  => 'path/to/Bootstrap.php',
        ),
        'config' => APPLICATION_PATH . '/configs/application.ini',
    ),
);
$app->bootstrap()
    ->run();
 

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?

Posted by Matthew Weier O'Phinney in PHP at 19:42 | Comments (14) | Trackback (1)
Defined tags for this entry: php, zend framework
Related entries by tags:
Module Bootstraps in Zend Framework: Do's and Don'ts
Responding to Different Content Types in RESTful ZF Apps
Symfony Live 2010
Creating Re-Usable Zend_Application Resource Plugins
Quick Start to Zend_Application_Bootstrap

Trackbacks
Trackback specific URI for this entry

Responding to Different Content Types in RESTful ZF Apps
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
Weblog: phly, boy, phly
Tracked: Mar 04, 15:30

Comments
Display comments as (Linear | Threaded)

If I get to choose the API to expose, I would go with REST.
#1 Sudheer (Link) on 2009-10-24 04:28 (Reply)
REST and RPC are two very different architectures, and each have their place. If your API is simply exposing a resource, REST is a great way to go; if, instead, your API is exposing discrete behaviors, RPC is the more appropriate direction. This article discusses RPC architectures specifically. I'll be writing about REST and ZF at a later date.
#1.1 Matthew Weier O'Phinney (Link) on 2009-10-24 17:19 (Reply)
I agree, Matthew. Doing some REST work for a while, I tend to think everything in terms of resources and their manipulation.

RPC has its place. I have used XMLRPC to aid software distribution before. It has worked well. I even wrote a series of blog posts on XMLRPC.

Generally, I lean towards REST.
#1.1.1 Sudheer (Link) on 2009-10-25 00:11 (Reply)
Nice examples, but I wonder why class names are passed instead of objects. What if my My_Awesome_Api's constructor requires parameters?
#2 Giorgio Sironi (Link) on 2009-10-24 12:39 (Reply)
What I show above is the simplest approach. Additionally, it has some benefits over attaching discrete objects: most of the servers allow attaching multiple classes/objects per server, and it doesn't make sense to provide class instances for objects that are not being requested.

That said, you can always pass a class instance to setClass(). You can also pass extra parameters to setClass(), and these will be passed to the class constructor at instantiation -- giving you the best of both worlds.
#2.1 Matthew Weier O'Phinney (Link) on 2009-10-24 17:22 (Reply)
I recently wrote a SOAP api in Zend Framework, as part of a site that uses the Zend MVC for its web.

My thoughts are that rather than having two competing forms of service layers, the controllers could implement the API, and there being different dispatchers translate the protocol. The parameters passed to the view can be translated into the response.

For instance, the controller actions could be used as SOAP actions, or controller.action could be translated for XML-RPC calls.

If the MVC pattern adds overhead, maybe consider streamlining it for web services?
#3 Andrew Thompson (Link) on 2009-10-26 10:52 (Reply)
As noted in the article, the server classes follow the SoapServer API, which was chosen for its simplicity, to promote code re-use, and to allow creating servers without the MVC (as not all ZF users utilize the MVC). Since RPC-style servers typically only listen to a single URL (unlike RESTful resources, which will map to several different URLs for the same resource), having them in the MVC is not necessary.

Additionally, the SoapServer API basically provides you the V and C of the MVC pattern by design: you attach models to the server, which acts as a controller, and then your response object serializes the content appropriately for your view.

This latter shows why the MVC is overhead: injecting RPC servers into your MVC structure is basically wrapping an MVC within an MVC. It's redundant -- and that's where the overhead truly lays.

Certainly, you could write your MVC controllers to act as services. However, this requires more effort on the end-user developer's part (vs: setClass()->handle()), and could lead to code duplication (if a service controller duplicated logic that was also being provided in a vanilla HTML action controller). Certainly, you can do some context switching, but the whole architecture becomes more problematic. The approach I outlined above is quite simple, and leads to a nice separation of concerns: MVC application for your HTML and REST endpoints, and specific scripts for RPC endpoints.
#3.1 Matthew Weier O'Phinney (Link) on 2009-10-26 11:18 (Reply)
You're right, I guess I just want to keep the dynamicness of the routing system and configuration for specifying endpoints.

I guess a way that would make sense is to just have the dispatcher construct and call the server's handle() method.
#4 Andrew Thompson (Link) on 2009-10-26 12:16 (Reply)
Great short article very usefull for me as I will start very soon with a couple of webservices with Zend framework. Alltought these service would be more about exposing resources. So looking forward to an article regarding Zend Framework and REST.
#5 Tom Staals (Link) on 2009-10-27 04:49 (Reply)
Hi Tom,

I am writing a series of posts on REST using Zend Framework. The first one is http://techchorus.net/create-restful-applications-using-zend-framework . I will be writing more in the coming weeks.

With ZF, you can take multiple approaches to build REST server.

It will be nice to read Matthew's posts on the topic as well.
#5.1 Sudheer (Link) on 2009-10-27 08:09 (Reply)
My rule of thumb for decision regarding webservices technology: if you don't now the consumer or the consumer could be anybody, use REST (with XML and JSON). If the consumer is a scripting language like Ruby, Python, PHP, use XML/RPC as it is easy to use. If Java and .NET, use SOAP. Flash only, go with AMF.
#6 Lars Strojny (Link) on 2009-10-28 06:43 (Reply)
Hey Matthew -

great post as always. Thank you very much!

I was wondering how to handle API versioning in an efficient way. The thing is that I don't want to break any existing functionality at least for a specific period of time. Considering the eBay API, they do ensure that a specific version of the API is available for a full year making sure that you do not need to update your client application every week. E.g. my application is based on API version 23, current one is 29, but 23 is still available to me with the well-known interface.

How would you do that when just pushing actual classes to the SOAP/RPC/Whatever Server component?

I am not getting my head around that ...

Thanks,
Michael
#7 Michael on 2009-11-10 11:28 (Reply)
Hey Matthew -

Nice post, but it would be really nice to get the complete source files i.s.o. snippets (in particular for a soap endpoint and client).

I think I got it right but I still get errors.

Thanks in advance,
John__.
#8 John Bouwens (Link) on 2010-01-13 08:57 (Reply)
Apparently, my problems were related to php 5.3. The WSDL is retrieved using HTTP. SoapClient in 5.3 is sending an HTTP 1.1 request but the SoapServer uses a HTTP 1.0 response. When you load the WSDL over HTTP there is no content-length header, as the response is sent chunk-encoded. Giving a "Premature end of data in tag definitions line 2" error.


On the server side. By subclassing Autodiscover with a class with to following code:

public function handle($request = false)
{
if (!headers_sent()) {
header('Content-Type: text/xml');
}
header('Content-Length: '.strlen($this->_wsdl->toXML()));
$this->_wsdl->dump();
}


And on the client side by adding the following options:

$context = stream_context_create ( array ('http' => array ('protocol_version' => '1.0',
'header' => 'Content-Type: text/xml;' ) ) );

$options = array ('stream_context' => $context, 'soap_version' => SOAP_1_1 );

try {
$client = new Zend_Soap_Client ( "http://some.domain.com:8080/api/v1/NeoHost.php?wsdl", $options );

$string = $client->hello ( 'John' );
var_dump ( $string );

} catch ( Zend_Exception $e ) {
echo $e->getMessage ();
}

The problems went away.

John__.
#8.1 John Bouwens (Link) on 2010-01-18 09:17 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back March '10 Forward
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

March 2010
February 2010
January 2010
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml rest
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net