Chapter 12. Talking with other applications – Zend Framework in Action

Chapter 12. Talking with other applications

This chapter covers
  • An introduction to web services
  • How to create and consume an RSS feed with Zend_Feed
  • Integrating a Zend_XmlRpc server into a Zend Framework application
  • Creating a REST server using Zend_Rest

This chapter will cover the use of various components of Zend Framework that can be loosely lumped together under the term “web services,” which the World Wide Web Consortium (W3C) defines as “a software system designed to support interoperable machine-to-machine interaction over a network.” For the sake of simplicity, we’ll base our use of the term “web services” in this chapter on that definition, rather than on the more specific W3C focus on it being a combination of SOAP and WSDL (Web Services Description Language).

Part of that interoperation includes the use of XML, but one of the benefits of using these components is that we don’t need to focus on XML itself. Zend Framework provides a series of tools that takes care of the formatting and protocol of the interaction, leaving you to focus on the logic using only PHP. If you take a moment to list all the formats available just for web newsfeeds, the benefits of not having to deal with that range of formats and their rate of change will be obvious.

Before we get into using the Zend Framework components, we’ll take a look at how we can and why we would integrate applications using web services.

12.1. Integrating Applications

It’s interesting just how much web services have become a part of our offline and online existence. Every time Nick starts up his computer, a newsreader fires up and digests a list of XML-formatted news from sites that he could never keep up with otherwise. Recently his wife sold some of the clutter from their garage using GarageSale, a Mac OS X desktop application that talks to eBay using its XML-based API over HTTP. The key to all of these actions is the exchange of information across a network, and the distribution of the computing.

12.1.1. Exchanging Structured Data

XML stands for Extensible Markup Language and originates from Standard Generalized Markup Language (SGML), which is one of the many markup languages whose role is simply to describe text. Probably the best known is HTML (Hypertext Markup Language), which describes text documents that are intended to be transmitted by HTTP.

Data does not need to be marked up to be exchanged, but in most cases it does need to have some kind of structure. Here are some examples:

  • comma- or tab-separated values (CSV or TSV)
  • structured text that bases its structure on a regular sequence of data separated by consistent delimiters
  • serialized data such as that created by PHP’s own serialize() function
  • other formats like JavaScript Object Notation (JSON), which can be used as an alternative to XML in AJAX (and which is accommodated for in Zend Framework by Zend_Json)

Little of this information should be new to readers of this book, but the point is we’re trying to pass information from one system to another such that the recipient system knows how to handle that data.

If we take a look at the GarageSale application in figure 12.1, it’s clearly a fairly complex application whose data could not be exchanged unless it were suitably structured so that eBay could process it and carry out whatever requests it makes, such as creating a new item for auction.

Figure 12.1. The GarageSale Mac OS X desktop application, which converses with eBay’s XML-based API using web services

Having looked at the formatting of the data involved in this discussion between applications, the next question is how the applications talk to each other.

12.1.2. Producing and Consuming Structured Data

Email serves as a good example of how applications talk to each other using a structured data format (internet email format) that is produced at one end, sent by a mail server (MTA), and consumed by the receiving email client (MUA) at the other end. The application conversations we’re looking at in this chapter take that basic concept a step further by using that exchange to trigger actions at either end through what’s known as a remote procedure call (RPC).

Later in this chapter, we’ll be covering Zend_XmlRpc, which uses Zend_XmlRpc_Server to send and receive XML-encoded RPCs over HTTP. Originally created by Dave Winer, XML-RPC is a surprisingly simple and flexible specification, which allows it to be used in numerous situations.

As functionality was added, XML-RPC evolved into SOAP (originally an acronym, but now just a word in itself) and has been adopted by the W3C. You don’t need to look too far to find complaints about how this added functionality has also added a lot more complexity, which partly explains why XML-RPC is still in use despite the official W3C adoption of SOAP. In some ways, SOAP is itself being superseded by other protocols like Atom. You can see the status of these protocols reflected in Zend Framework itself; Zend_Soap lingered in the incubator for over two years before being finalized, due in large part to lack of interest by users, while XML-RPC and Atom are both included in the core.

We’ll look at Zend Framework’s components shortly, but first we need to cover how web services work and why we would use them.

12.1.3. How Web Services Work

The fastest way to present the simplicity of web services is with an illustration like figure 12.2. At their most basic, web services work like many other data-transmission methods, formatting some data (such as into XML format with XML-RPC) at one end, passing it via some protocol (HTTP, in this case), and using it at the other end.

Figure 12.2. The basic web service transaction between two systems using XML-RPC

Of course, that explanation is so generic as to be fairly worthless, so to further illustrate how web services work, let’s follow the steps of an XML-RPC example in which a desktop application needs to get updated prices from an online service:

  1. A desktop application gathers the data required to make a procedure call (including the ID of the requested item and the remote procedure that gets the prices for items). The XML-RPC client component of the desktop application encodes this remote procedure call into XML format and sends it to the online service like so:
    <?xml version="1.0"?>
    <methodCall>
    <methodName>onlineStore.getPriceForItem</methodName>
    <params>
    <param>
    <value><i4>123</i4></value>
    </param>
    </params>
    </methodCall>
  2. The XML-RPC server of the online service receives the XML-encoded procedure call and decodes it into a format that the system code can process, such as $store->getPriceForItem(123). The system code returns the requested price to its XML-RPC server, which encodes that as an XML response and sends it back to the requesting desktop application:
    <?xml version="1.0"?>
    <methodResponse>
    <params>
    <param>
    <value><double>19.95</double></value>
    </param>
    </params>
    </methodResponse>
  3. The desktop application receives the response, decodes it into a format it can process, and updates the price for item 123 to $19.95.

That should give you an idea of how web services work, but the question remains as to why we need to use them.

12.1.4. Why We Need Web Services

So why do we need web services? The simple answer is also the most ironic: we need web services so that applications running on different platforms or frameworks can talk to each other in a standard way. This chapter has already started to point out the irony in that concept by, in all likelihood, confusing you with a small selection of the varied protocols that make up these “standards.”

Pushing this cynicism aside, and returning to the example of our desktop application fetching updated prices from the online service, you’ll notice that there was little detail about how each end of the transaction actually performed its procedure calls. The reason is it doesn’t matter, because as long as each application is able to convert its internal processes into a standard form of communication—the XML-RPC protocol—the transaction can be completed.

Clearly this can lead to very powerful interactions, such as the one between Garage-Sale and eBay. In this chapter and the next, we’ll look at examples of how Zend Framework components can bypass some of the complexities of web services to take advantage of such interactions.

We’ll start with an example that most readers are probably familiar with: web feeds and the benefits of using Zend_Feed to produce and consume them.

12.2. Producing and Consuming Feeds with Zend_Feed

Zend Framework’s online manual describes Zend_Feed as providing “functionality for consuming RSS and Atom feeds.” We’re going to start from the opposite end of that transaction by showing how it can also be used to produce RSS and Atom feeds. We’ll then look at examples of consuming web feeds.

12.2.1. Producing a Feed

If you took up the challenge mentioned at the start of this chapter to list all the formats available for web feeds, you’d have started with the RDF or RSS 1.* branch, which includes RSS 0.9, 1.0, and 1.1. You’d then have moved to RSS 2.*, which includes RSS 0.91, 0.92, and 2.0.1. From there you’d have followed on to the Atom syndication format. If you did that while working under the pressure of a deadline, and then discovered that all these formats are currently in use across millions of syndicated sites, you’d have probably sat down in a cold sweat!

Fortunately, all you need to do is pick the latest and greatest formats and concentrate on outputting those. But even that isn’t needed, because Zend_Feed can take care of the format for you; you just need to pass it the data for your feed. Listing 12.1 demonstrates a very simple controller action that produces an RSS (2.0) or Atom feed from the articles in our Places application.

Listing 12.1. A feed-producing controller action

In listing 12.1, we begin by determining the feed format, and if neither RSS or Atom feed format is requested, we default to RSS . Next, we grab a selection of articles from the database to insert into the feed (this would likely be limited, but it is simplified for this example) . A multidimensional array consisting of the <channel> element is then constructed , with each <item> being added by looping over the rowset retrieved from the Articles table . That array is then imported into Zend_Feed, along with the format in which it is to be encoded , then it is outputted as an XML string ready to be digested by a newsfeed reader or aggregator .

Note that we use the send() method, which sets the content type of the HTTP header string to something like this:

  Content-Type: text/xml; charset=UTF-8

If we were using the feed in some other way, we could just use the following line to get the XML string without the HTTP headers:

  $feed->saveXml()

Finally, because we’re generating an XML string and using this in a controller action, we disable the automatic view and layout rendering .

In figure 12.3, we can see that Firefox recognizes this as a web feed, shows its parsed content, and asks if we’d like to subscribe to it through its Live Bookmarks. It should be noted, that the feed we’ve produced is a little too minimal, and we’d likely need to add further elements for it to work with other readers and aggregators. We’ve kept it simple for the sake of clarity.

Figure 12.3. The feed we produced as it appears in the Firefox web browser, together with the XML source

Now that we’ve produced a feed with Zend_Feed, we can move on to consuming that feed.

12.2.2. Consuming a Feed

While working on this chapter, Nick mentioned using web feeds to supplement a directory site with news retrieved from the websites of its listings. In that particular case, each listing had the URL of its feed stored along with its other data. If that site had been built using Zend Framework, storing the specific feed URL would have been unnecessary, because Zend_Feed is able to parse any HTML page and search for the same link elements that modern browsers use to indicate the presence of a feed, like these:

  <link rel="alternate" type="application/rss+xml"
       title="Places RSS Feed" href="/feed/index/format/rss/" />
  <link rel="alternate" type="application/atom+xml"
       title="Places Atom Feed" href="/feed/index/format/atom/" />

All that is required is a single line of code:

  $feedArray = Zend_Feed::findFeeds('http://places/');

Since we are using the feed from the example we produced earlier, we already know the URL, and the code to consume that feed is straightforward because it imports directly from that URL:

  $this->view->feed = Zend_Fed::import(
                 'http://places/feed/index/format/rss/'
              );

The elements of that feed could then be presented in a view, like so:

  <h1>
     <a href="<?php echo $this->feed->link(); ?>">
        <?php echo $this->feed->title(); ?>
     </a>
  </h1>
  <div class="channeldesc"><?php echo $this->feed->description(); ?></div>
  <?php foreach ($this->feed as $item): ?>
     <h2>
        <a href="<?php echo $item->link; ?>">
           <?php echo $item->title(); ?>
        </a>
     </h2>
     <div class="itemdesc"><?php echo $item->description(); ?></div>
  <?php endif; ?>

While we’re covering the methods of consuming feeds, it would be remiss of us not to mention the remaining methods, such as importing from a text file:

  $cachedFeed = Zend_Feed::importFile('cache/feed.xml');

And here’s an example of importing from a PHP string variable:

  $placesFeed = Zend_Feed::importString($placesFeedString);

While we’ve not covered all the features of Zend_Feed, we have covered those you are likely to use most of the time. Having done so let’s move on to the next section on Zend_XmlRpc.

12.3. Making RPCs with Zend_XmlRpc

We already described how XML-RPC makes XML-encoded RPCs through HTTP requests and responses. The relative youth of XML might suggest this is yet another new technology being thrust upon us by marketing departments, but RPCs are not a new concept. Written over thirty years ago, RFC 707, “A High-Level Framework for Network-Based Resource Sharing,” describes the RPC protocol in a slightly quaint way:

Given such a protocol, the various remote resources upon which a user might wish to draw can indeed be made to appear as a single, coherent workshop by interposing between him and them a command language interpreter that transforms his commands into the appropriate protocol utterances.

—RFC 707, “A High-Level Framework for Network-Based Resource Sharing,” January 14, 1976

The start of RFC 707 makes an interesting challenge to ARPANET, the predecessor to today’s internet: “This paper outlines an alternative to the approach that ARPANET system builders have been taking”. While it is interesting to consider how the internet might look now had that alternative approach been taken, the key point is that RPC is one solution among many, including the internet itself, for allowing disparate applications to talk to each other.

Just as a further note, because RPC mediates between one application and another, it can be classified as middleware, which isn’t particularly interesting until you notice that amongst the others in that classification is SQL.

Clearly XML-RPC has enough credentials to add to any proposal: it’s based on technology established over thirty years ago, which was partly proposed as an alternative to today’s internet, and it shares the same problem-solving area as the language through which we converse with databases.

Having determined that XML-RPC has a suitable lineage and, with the addition of XML, enough youth to keep it vibrant, we can move on to using Zend Framework’s implementation. The example we’re going to work through is an implementation of the various blog APIs that allow blog editors an alternative method of adding, editing, and deleting blog entries via desktop or other remote applications. We’ll start by setting up an XML-RPC server using Zend_XmlRpc_Server.

12.3.1. Using Zend_XmlRpc_Server

Zend_XmlRpc_Server is used to implement the single point of entry for XML-RPC requests, and in that respect it acts much like Zend Framework’s front controller does. In fact, you can make your XML-RPC server a controller action that receives its request via the front controller, but that would involve a lot of unnecessary processing overhead. Instead, we’re going to separate out parts of the bootstrapping process and build our server capabilities on top of that.

Setting Up the Bootstrapping

If you’ve read previous chapters, you’ll already be aware that the Zend Framework MVC structure relies on mod_rewrite settings in a .htaccess file to pass all requests via a front controller file like index.php. Since our XML-RPC server has its own single point of entry, we need to exclude it from that rewrite rule. In listing 12.2 we do that by adding a rewrite condition that excludes any requests for /xmlrpc from the final rewrite rule that passes requests to index.php.

Listing 12.2. Rewrite rules modified to allow requests through to the XML-RPC server

Alternatively, figure 12.4 illustrates a solution you’ll appreciate if, like Nick, you’re a coward when it comes to the mystical arts of mod_rewrite.

Figure 12.4. The directory structure with our xmlrpc controller file

What we’ve done in figure 12.4 is added a .htaccess file with one line within our xmlrpc directory:

RewriteEngine off

This means we can drop our xmlrpc directory into any of the applications we’re working on, and any requests to /xmlrpc/ will go to our xmlrpc/index.php file. This means that we don’t have to tamper with what could be a finely tuned .htaccess file belonging to the main application or several applications, each with potentially varying rewrite settings.

Now that requests are successfully getting to index.php, we can add the code to forward those requests on to the bootstrap file in our application directory. Readers of the previous chapter will be familiar with this setup. Note the call to a new method, runXmlRpc(), shown in listing 12.3.

Listing 12.3. The contents of our xmlrpc/index.php file

Having got all that together, we’re now finally able to get to the main topic of this section: using Zend_XmlRpc_Server. Listing 12.4 shows a stripped-down version of the bootstrap file, which includes the runXmlRpc() method that was called from our index.php file.

Listing 12.4. Using Zend_XmlRpc_Server in our bootstrap file

We’ve intentionally left out the contents of the constructor in listing 12.4, but we’ve included the method to show where we would introduce configuration settings, set include paths, establish a database connection, and perform any other application-specific initialization. Setting up the server is then a fairly straightforward process of instantiating the server object, supplying that server with the class methods that will become its method handlers, handling the XML RPC, and echoing back the response.

One thing you might notice is that when we set the method handlers we also passed in a namespace string. The example classes that we’re working with demonstrate exactly why this is important—both the Blogger and Metaweblog classes contain editPost() methods that would clash without the ability to namespace them as metaWeblog.editPost and blogger.editPost.

With the server set up and ready to go, we can now elaborate on the class methods that the server will be using.

Creating the XML-RPC Method Handlers

Since the purpose of our XML-RPC server is to receive RPCs, our next step is to create those procedures. As already mentioned, we’ll be implementing some of the APIs for several of the blog applications so that a desktop application can work with articles on our Places application.

First, let’s take a look at the Ecto application we’ll be working with, which describes itself as “a feature-rich desktop blogging client for Mac OS X and Windows, supporting a wide range of weblog systems.” The main reason we chose Ecto was it has a console, which makes it very useful when debugging our XML-RPC transactions, but most of what we discuss in this section is applicable to other similar applications. In figure 12.5, we can see Ecto’s main window on the left, with a list of Places articles, and its editing window on the right.

Figure 12.5. Ecto, the desktop blogging client we’ll be using to make XML-RPC requests, shown editing some Places content.

After we set up the connection details and select the API we want to connect to, Ecto runs through a series of method calls to establish the account. We’ve chosen to use the MovableType API, but doing so still involves calls to methods that belong to other APIs, such as blogger.getUsersBlogs and blogger.getUserInfo, which belong to the Blogger API, or metaWeblog.editPost, which belongs to the MetaWeblog API. For this reason, we’re obliged to provide those methods for our XML-RPC server, and that obligation is an indication that we need to create interfaces.

Creating the Interfaces

In order to work with any of the APIs already mentioned, our application must implement the methods required by those APIs. If we pick an example method like metaWeblog.editPost, the requirement is that it returns a Boolean value and consumes the following parameters:

  metaWeblog.editPost (postid, username, password, struct, publish)

How our application carries out the processing of this request depends on the application itself, but the fact that it must implement the required methods clearly calls for object interfaces. According to the PHP manual, “object interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are handled.” Listing 12.5 shows one of our interfaces establishing all the methods of the MetaWeblog API.

Listing 12.5. Our interface for the MetaWeblog API
interface Places_Service_Metaweblog_Interface
{
   public function newPost(
              $blogid, $username, $password, $content, $publish
              );
   public function editPost(
              $postid, $username, $password, $content, $publish
              );
   public function getPost($postid, $username, $password);
   public function newMediaObject(
              $blogid, $username, $password, $struct
              );
   public function getCategories(
              $blogid, $username, $password
              );
   public function getRecentPosts(
              $blogid, $username, $password, $numposts
              );
}

This interface was quite straightforward to set up, because it’s clearly detailed in the MetaWeblog spec. The XML-RPC Blogger API and MovableType APIs are a bit more fiddly because they have actually been deprecated, despite still being used in many current web and desktop applications.

Having set up an example interface, we can use it in our concrete classes, where it’ll ensure that those classes will adhere to the requirements of the original API.

Creating the Concrete Classes

Since our original shot of Ecto in figure 12.5 showed us editing an article on Places, we’ll continue that theme and use the MetaWeblog editPost() method to demonstrate both the use of our interface and how methods need to be set up so Zend_XmlRpc_Server can use them.

Listing 12.6 shows a stripped-down version of the Metaweblog class that resides in our models/ directory.

Listing 12.6. Our MetaWeblog model implementing the interface from listing 12.5

This model class implements our Metaweblog interface , so the final version would have to include all the methods in the interface, but for the sake of space they have not been included here. Similarly, only the objects needed for our editPost() method have been instantiated in our constructor , but the full version would need more.

DocBlocks are the key to getting methods working with Zend_XmlRpc_Server. We’ll go into more detail about their importance in this section, but their key role is to determine the method help text and method signatures. In our example, you can see that they indicate the type, variable name, and description of each parameter and the return value . The parameters of our method must then match those in the DocBlock , though our interface will also enforce the parameters.

For security, we do a rudimentary authorization check using the username and password passed in the parameters with a custom auth class , and we throw an exception upon failure. Data used to build the array used in the update query is also filtered where necessary . If all is well, we update the database row that corresponds to the provided ID , and, as specified in the return value of the DocBlock, we return a Boolean.

After a simple authentication check using the username and password passed in the parameters, this editPost() method filters and formats an array from the received data and updates the database row that corresponds to the provided ID on success , or raises an exception that will be handled by Zend_XmlRpc_Server on failure.

You’ll notice that the editPost() method is only minimally different from any standard method in that it has the DocBlock parameter data type struct, which isn’t a native PHP data type. When you use Zend_XmlRpc_Server::setClass() or Zend_XmlRpc_Server::addFunction(), Zend_Server_Reflection checks all methods or functions and determines their method help text and signatures using the DocBlocks.

In table 12.1, we can see that in the case of @param $struct, the data type has been set to the struct XML-RPC type that corresponds to the associative array PHP type, which is processed using the Zend_XmlRpc_Value_Struct object.

Table 12.1. Mapping PHP types to their XML-RPC types and Zend_XmlRpc_Value objects

PHP native type

XML-RPC type

Zend_XmlRpc_Value object

Boolean <boolean> Zend_XmlRpc_Value_Boolean
Integer <int> or <i4> Zend_XmlRpc_Value_Integer
Double <double> Zend_XmlRpc_Value_Double
String <string> (the default type) Zend_XmlRpc_Value_String
dateTime.iso8601 <dateTime.iso8601> Zend_XmlRpc_Value_DateTime
Base64 <base64> Zend_XmlRpc_Value_Base64
Array <array> Zend_XmlRpc_Value_Array
Associative array <struct> Zend_XmlRpc_Value_Struct

Using these mappings, we can, at any time, call on a Zend_XmlRpc_Value object to prepare values for XML-RPC. This is often needed when preparing values in an array, for example:

  array('dateCreated' => new Zend_XmlRpc_Value_DateTime(
       $row->date_created, Zend_XmlRpc_Value::XMLRPC_TYPE_DATETIME);

The preceding example formats a date from a database table row into the ISO8601 format required by XML-RPC. The second parameter refers to the class constant XMLRPC_TYPE_DATETIME, which is unsurprisingly defined as dateTime.iso8601.

If you’re thinking that all this introspection by Zend_Server_Reflection must come at a price, you’d be right, particularly when a lot of classes or functions are attached to the server. Thankfully, there is a solution in the form of Zend_XmlRpc_Server_Cache, which, as the name implies, can be used to cache the information gathered by Zend_Server_Reflection. We don’t need to change too much from the example given in the Zend Framework manual, because Zend_XmlRpc_Server_Cache is very simple to use, as shown in listing 12.7.

Listing 12.7. Our Zend_XmlRpc_Server with caching implemented

With Zend_XmlRpc_Server_Cache in place, we can cut out all that resource-intensive introspection. If we need to change the code in any of the attached classes, we need only delete the cache file so that a new version that reflects the changes can be rewritten.

Now that we’ve set up our XML-RPC server, we can look at the client side of the exchange with Zend_XmlRpc_Client.

12.3.2. Using Zend_XmlRpc_Client

For our Places application, our intention was to set up an XML-RPC server so that we could remotely edit articles with any of the desktop applications that support the blog APIs. In doing so, we’ve covered a lot of the functionality of Zend_XmlRpc. In this section, we’ll demonstrate those functions a little further by using Zend_XmlRpc_Client to simulate a client request to the editPost() method we demonstrated in listing 12.6.

Unlike Zend_XmlRpc_Server, which was used within its own front controller, we’re using Zend_XmlRpc_Client within a controller action, as shown in listing 12.8.

Listing 12.8. Using Zend_XmlRpc_Client within a controller action

In this example, we’re filtering the data from an HTML form and preparing it for use by the client, which has been set up with the URL of the XML-RPC server we’ll be sending requests to. The Zend_XmlRpc_Client server proxy object then handles the rest by compiling the XML-encoded request and sending it via HTTP to the XML-RPC server it was instantiated with.

Borrowing Ecto’s console window, we can illustrate the process more thoroughly in figure 12.6. There we can see the XML-encoded request in the left console window, which is processed by the XML-RPC server. If it’s successful, it will update the article in the middle and return an XML-encoded response with the Boolean value true back to the client.

Figure 12.6. Demonstrating the editPost() method call with the request on the left, the updated article in the middle, and the response returned from the XML-RPC server on the right.

Not all XML-RPC requests are successful, of course, and for that reason Zend_XmlRpc_Client has the ability to handle HTTP and XML-RPC errors through exceptions. In listing 12.9 we add error handling to the editPost() procedure call from listing 12.8.

Listing 12.9. Adding error handling to our XML-RPC client

Our client now attempts to make the RPC and on failure can handle HTTP and XMLRPC errors. Note that because we’re not actually using the client in our Places application, we’ve not gone into any detail about what we’d do with those error messages.

As we’ve demonstrated in this section, XML-RPC is quite straightforward to set up and use, and Zend_XmlRpc makes it even easier. However, like any technology, XMLRPC has its critics, and in the next section we’ll look at another approach to web services using Zend_Rest.

12.4. Using REST Web Services with Zend_Rest

At the start of this chapter, we mentioned that the various Zend Framework components we’d be working with would include XML but that we wouldn’t be needing to deal with it directly. The Zend_Rest section of the manual opens by stating that “REST Web Services use service-specific XML formats,” which is true, but it needs a little clarification because REST web services don’t care whether or not they use XML. Zend_Rest does use XML as its format of choice, but it’s possible to circumvent this if you dig around a bit, which we’ll do after looking at REST in a bit more detail.

12.4.1. What is REST?

REST stands for Representational State Transfer and was originally outlined in “Architectural Styles and the Design of Network-based Software Architectures” by Roy Fielding, whose role as “primary architect of the current Hypertext Transfer Protocol” explains some of the background to REST. As Fielding states in the document, “REST ignores the details of component implementation and protocol syntax in order to focus on the roles of components, the constraints upon their interaction with other components, and their interpretation of significant data elements.” He also states that “the motivation for developing REST was to create an architectural model for how the Web should work, such that it could serve as the guiding framework for the Web protocol standards.” In other words, every time we make an HTTP request, we’re using a transfer protocol based on the REST concept.

While the key element of RPC is the command, usually accessed via a single point of entry, the key element in REST is the resource, an example of which could be the Places login page, whose resource identifier is the URL http://places/auth/login/. Resources can change over time (for example, our login page could have interface updates), but the resource identifiers should remain valid.

Of course, having a lot of resources doesn’t hold a great deal of value unless we can do something with them, and in the case of web services we have the HTTP methods. To illustrate the value that such apparently simple operations have, Table 12.2 compares the HTTP methods POST, GET, PUT, and DELETE with the common generic database operations: create, read, update, and delete (CRUD).

Table 12.2. Comparing HTTP methods used in REST with common generic database operations

HTTP methods

Database operations

POST Create
GET Read
PUT Update
DELETE Delete

All readers of this book will likely be familiar with the POST and GET HTTP request methods, whereas PUT and DELETE are less well known, partly because they’re not often implemented by all HTTP servers. This limitation is reflected in Zend_Rest, because Zend_Rest_Client is able to send all of these request methods but Zend_Rest_Server will only respond to GET and POST.

Presuming that readers will already know enough HTTP to be able to join the dots on the fundamentals, we’ve kept this introduction to REST intentionally brief. Hopefully, as we start to explore some of the REST components of Zend Framework, things will become clearer. We’ll start with Zend_Rest_Client.

12.4.2. Using Zend_Rest_Client

As we mentioned at the start of this section, Zend_Rest uses XML to serialize the data it processes in the body of the HTTP request or response, but not all RESTful web services use XML. One example is the Akismet spam-filtering service, which we’ll use to demonstrate the various ways of accessing REST-based web services. Figure 12.7 shows where we could use the service to check the reviews submitted by users of our Places application to make sure spam replies are kept out.

Figure 12.7. The reviews in our Places application, which we could filter using the Akismet spam-filtering service

Another reason we’ve chosen Akismet is that Zend Framework has a Zend_Service_Akismet component, which means you won’t be left with a partially implemented solution and will hopefully be able to understand how that component works.

The Akismet API provides a small choice of resources, shown in table 12.3, from which we’ll choose the resource to verify that the API key, which is required to use Akismet and is obtained from Wordpress.com, is valid.

Table 12.3. The resources provided by the Akismet API

Resource identifier

Description

rest.akismet.com/1.1/verify-key Verifies the required API key.
api-key.rest.akismet.com/1.1/comment-check Identifies whether or not the submitted comment is spam.
api-key.rest.akismet.com/1.1/submit-spam Submits missed spam.
api-key.rest.akismet.com/1.1/submit-ham Submits content incorrectly marked as spam.

The first thing we’ll do is attempt to use Zend_Rest to access this resource in the method demonstrated in the manual. You can see this in listing 12.10.

Listing 12.10. Sending a REST request to Akismet using Zend_Rest_Client

In this example, we construct our client with the URL of the verify key resource, then, using the fluent interface that is common in many Zend Framework components, we set the two required variables key and blog using the built-in magic methods. Finally, we send them via an HTTP POST request. Unfortunately, despite the pleasing simplicity of this code, the resulting request will fail because Akismet doesn’t return the XML response that is expected by Zend_Rest_Client when used this way.

Since all Akismet needs is a simple HTTP request, and Zend_Rest itself uses Zend_Http_Client, why can’t we just use Zend_Http_Client in the first place? The answer, as demonstrated in listing 12.11, is that we can.

Listing 12.11. Sending a REST request to Akismet using Zend_Http_Client

Zend_Http_Client won’t attempt to parse the returned response from Akismet as if it were XML, and we’ll receive the plain text response valid or invalid depending on whether or not our data is verified successfully.

If Zend_Http_Client can successfully make the request, surely there is a way that Zend_Rest can do the same? Of course there is, and in listing 12.12, which should be looking familiar by now, we bypass having Akismet’s plain text response parsed as XML by calling the restPost() method directly. Unlike our first attempt, this method returns the body of the HTTP response rather than sending it through Zend_Rest_Client_Result.

Listing 12.12. Sending a REST request to Akismet using Zend_Rest

Having solved our problem with Akismet’s service, we now know we can use Zend_Rest_Client with plain-text-based and XML-based RESTful web services. If we wanted to work with the rest of Akismet’s resources, it would obviously make more sense to use Zend_Service_Akismet, but if there weren’t a prebuilt Zend Framework component we’d have several options. One of those is to use Zend_Rest_Server to interact with REST-based web services provided by our own application server.

12.4.3. Using Zend_Rest_Server

Let’s imagine that we’ve convinced one of the advertisers on Places, Edinburgh Zoo, to take part in a joint promotion that will be hosted separately from both of our sites. The idea is to make a short lifespan mashup site based on content from Places, Edinburgh Zoo, and other interested sites.

As we did with our XML-RPC server earlier, we’ll start by making a simple interface called Places_Service_Place_Interface to make sure our server has a consistent API. Listing 12.13 shows our interface with two methods: one to get a place and one to get the reviews for a place.

Listing 12.13. Our Places service application interface
interface Places_Service_Place_Interface
{
   public function getPlace($id);
   public function getReviews($id);
}

In listing 12.14, we make a concrete implementation of our interface using queries similar to those from Chapter 6. Note that the database results are returned as an array rather than as the default objects, which would have failed when processed by Zend_Rest_Server. Another thing you may notice is that unlike Zend_XmlRpc_Server, Zend_Rest_Server does not require parameters and return values specified in DocBlocks even though it will use them if provided.

Listing 12.14. Our Places service concrete class

Now that we have the concrete class, we can attach it to Zend_Rest_Server with the same approach that we covered with Zend_XmlRpc_Server. Listing 12.15 shows our REST server set up within an action controller and accessible via an HTTP GET or POST that must supply the name of the service method you wish to invoke.

Listing 12.15. Our REST server
class RestController extends Zend_Controller_Action
{
   protected $_server;

   public function init()
   {
      $this->_server = new Zend_Rest_Server();
      $this->_helper->viewRenderer->setNoRender();
   }
   public function indexAction()
   {
      require_once 'ServicePlaces.php';
      $this->_server->setClass('ServicePlaces');
      $this->_server->handle();
   }
}

Figure 12.8 shows the XML-formatted results of some example GET requests to the resources http://places/rest/?method=getPlace&id=6 (on the left) and http://places/rest/?method=getReviews&id=6 (on the right) using Firefox.

Figure 12.8. The results of our REST server query: getPlace on the left and getReviews on the right

All our mashup site needs to do is use Zend_Rest_Client to make requests like the following:

  $client = new Zend_Rest_Client('http://places/rest/?method=getPlace');
  $response = $client->id('6')->get();

This will return a Zend_Rest_Client_Result object that allows us to access the elements of the response as properties that can be used in our site like so:

  echo $response->name; // Outputs "Edinburgh Zoo"

Having worked through the implementation of Zend_XmlRpc_Server, itself relatively simple when compared to, say, SOAP, you’ll have found Zend_Rest_Server very easy to follow. As with any brief introduction, there is a lot that has not been covered, such as PUT and DELETE HTTP requests, which are not handled by Zend_Rest_Server, and authentication. However, what we’ve set up is a REST server whose strength lies in the relationship between Zend_Rest_Server and Zend_Rest_Client and the simplicity of implementation.

12.5. Summary

This chapter has taken quite a concentrated look at some of the web service components of Zend Framework. The emphasis has been on the client/server relationships that we set up first by using Zend_Feed to generate a newsfeed of our Places articles and then by consuming that newsfeed. Next, we set up Zend_XmlRpc_Server to allow remote editing of Places articles through the blog APIs, and we followed that with an example of how Zend_XmlRpc_Client can make the RPCs to that server. Finally, we covered Zend_Rest, first filtering comments through Akismet’s REST-based spam-filtering service using Zend_Rest_Client and then providing our own API to Places content with Zend_Rest_Server.

Hopefully you’ll leave this chapter with a decent understanding of how web services work and be able to implement useful services using the various Zend Framework components. You should also be well prepared for the next chapter, where we’ll use the more specific components with some of the publicly available web services.