
Advanced PHP Programming
.pdf
398 Chapter 16 RPC: Interacting with Remote Services
Element |
Description |
title |
The title of the item |
link |
A URL that links to a formatted form of the item. |
description |
A summary of the item. |
author |
The name of the author of the item. In the RSS spec, this is speci- |
|
fied to be an email address, although nicknames are more common- |
|
ly used. |
pubDate |
The date the entry was published. |
The specification also optionally allows for fields for links to comment threads, unique identifiers, and categories. In addition, many Web logs extend the RSS item definition to include a content:encoded element, which contains the full post, not just the post summary that is traditionally found in the RSS description element.
To implement the MetaWeblog API, you need to define functions to implement the three methods in question. First is the function to handle posting new entries:
function metaWeblog_newPost($message) { $username = $message->params[1]->getval(); $password = $message->params[2]->getval();
if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, ‘Authentication Failed’);
}
$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval(); $entry[‘title’] = $item_struct[‘title’]; $entry[‘body’] = $item_struct[‘description’]; $entry[‘author’] = $username;
$entry[‘isdraft’] = ($publish == 0)?’true’:’false’; $id = serendipity_updertEntry($entry);
return new XML_RPC_Response( new XML_RPC_Value($id, ‘string’));
}
metaWeblog_newPost() extracts the username and password parameters from the request and deserializes their XML representations into PHP types by using the getval() method.Then metaWeblog_newPost() authenticates the specified user. If the user fails to authenticate, metaWeblog_newPost() returns an empty XML_RPC_Response object, with an “Authentication Failed” error message.
If the authentication is successful, metaWeblog_newPost() reads in the item_struct parameter and deserializes it into the PHP array $item_struct, using getval(). An array defining Serendipity’s internal entry representation is constructed from $item_struct, and that is passed to serendipity_updertEntry(). XML_RPC_Response, consisting of the ID of the new entry, is returned to the caller.

XML-RPC 399
The back end for MetaWeblog.editPost is very similar to MetaWeblog.newPost.
Here is the code:
function metaWeblog_editPost($message) { $postid = $message->params[0]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval();
if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, ‘Authentication Failed’);
}
$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval();
$entry[‘title’] |
= $item_struct[‘title’]; |
|
$entry[‘body’] |
= |
$item_struct[‘description’]; |
$entry[‘author’] |
|
= $username; |
$entry[‘id’] |
= |
$postid; |
$entry[‘isdraft’] = ($publish == 0)?’true’:’false’; $id = serendipity_updertEntry($entry);
return new XML_RPC_Response( new XML_RPC_Value($id?true:false, ‘boolean’));
}
The same authentication is performed, and $entry is constructed and updated. If serendipity_updertEntry returns $id, then it was successful, and the response is set to true; otherwise, the response is set to false.
The final function to implement is the callback for MetaWeblog.getPost.This uses serendipity_fetchEntry() to get the details of the post, and then it formats an XML response containing item_struct. Here is the implementation:
function metaWeblog_getPost($message) { $postid = $message->params[0]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval();
if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, ‘Authentication Failed’);
}
$entry = serendipity_fetchEntry(‘id’, $postid);
$tmp = array(
‘pubDate’ => new XML_RPC_Value( XML_RPC_iso8601_encode($entry[‘timestamp’]), ‘dateTime.iso8601’),
‘postid’ => new XML_RPC_Value($postid, ‘string’),
‘author’ => new XML_RPC_Value($entry[‘author’], ‘string’),
‘description’ => new XML_RPC_Value($entry[‘body’], ‘string’),
‘title’ => new XML_RPC_Value($entry[‘title’],’string’),
‘link’ => new XML_RPC_Value(serendipity_url($postid), ‘string’)
);


XML-RPC 401
Auto-Discovery of XML-RPC Services
It is nice for a consumer of XML-RPC services to be able to ask the server for details on all the services it provides. XML-RPC defines three standard, built-in methods for this introspection:
n system.listMethods—Returns an array of all methods implemented by the server (all callbacks registered in the dispatch map).
n system.methodSignature—Takes one parameter—the name of a method—and returns an array of possible signatures (prototypes) for the method.
n system.methodHelp—Takes a method name and returns a documentation string for the method.
Because PHP is a dynamic language and does not enforce the number or type of arguments passed to a function, the data to be returned by system.methodSignature must be specified by the user. Methods in XML-RPC can have varying parameters, so the return set is an array of all possible signatures. Each signature is itself an array; the array’s first element is the return type of the method, and the remaining elements are the parameters of the method.
To provide this additional information, the server needs to augment its dispatch map to include the additional info, as shown here for the metaWeblog.newPost method:
$dispatches = array(
‘metaWeblog.newPost’ =>
array(‘function’ => ‘metaWeblog_newPost’,
‘signature’ => array( array($GLOBALS[‘XML_RPC_String’],
$GLOBALS[‘XML_RPC_String’], $GLOBALS[‘XML_RPC_String’], $GLOBALS[‘XML_RPC_String’], $GLOBALS[‘XML_RPC_Struct’], $GLOBALS[‘XML_RPC_String’]
)
),
‘docstring’ => ‘Takes blogid, username, password, item_struct ‘.
‘publish_flag and returns the postid of the new entry’),
/* ... */
);
You can use these three methods combined to get a complete picture of what an XMLRPC server implements. Here is a script that lists the documentation and signatures for every method on a given XML-RPC server:
<?php
require_once ‘XML/RPC.php’;
if($argc != 2) {
print “Must specify a url.\n”;

402 Chapter 16 RPC: Interacting with Remote Services
exit;
}
$url = parse_url($argv[1]);
$client = new XML_RPC_Client($url[‘path’], $url[‘host’]); $msg = new XML_RPC_Message(‘system.listMethods’); $result = $client->send($msg);
if ($result->faultCode()) { echo “Error\n”;
}
$methods = XML_RPC_decode($result->value()); foreach($methods as $method) {
$message = new XML_RPC_Message(‘system.methodSignature’,
array(new XML_RPC_Value($method)));
$response = $client->send($message)->value(); print “Method $method:\n”;
$docstring = XML_RPC_decode( $client->send(
new XML_RPC_Message(‘system.methodHelp’,
array(new XML_RPC_Value($method))
)
)->value()
); if($docstring) {
print “$docstring\n”;
}
else {
print “NO DOCSTRING\n”;
}
$response = $client->send($message)->value(); if($response->kindOf() == ‘array’) {
$signatures = XML_RPC_decode($response); for($i = 0; $i < count($signatures); $i++) {
$return = array_shift($signatures[$i]); $params = implode(“, “, $signatures[$i]);
print “Signature #$i: $return $method($params)\n”;
}
}else {
print “NO SIGNATURE\n”;
}
print “\n”;
}
?>

SOAP 403
Running this against a Serendipity installation generates the following:
> xmlrpc-listmethods.php http://www.example.org/serendipity_xmlrpc.php
/* ... */
Method metaWeblog.newPost:
Takes blogid, username, password, item_struct, publish_flag and returns the postid of the new entry
Signature #0: string metaWeblog.newPost(string, string, string, struct, string)
/* ... */
Method system.listMethods:
This method lists all the methods that the XML-RPC server knows how to dispatch
Signature #0: array system.listMethods(string)
Signature #1: array system.listMethods()
Method system.methodHelp:
Returns help text if defined for the method passed, otherwise returns an empty string
Signature #0: string system.methodHelp(string)
Method system.methodSignature:
Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature) Signature #0: array system.methodSignature(string)
SOAP
SOAP originally stood for Simple Object Access Protocol, but as of Version 1.1, it is just a name and not an acronym. SOAP is a protocol for exchanging data in a heterogeneous environment. Unlike XML-RPC, which is specifically designed for handling RPCs, SOAP is designed for generic messaging, and RPCs are just one of SOAP’s applications. That having been said, this chapter is about RPCs and focuses only on the subset of SOAP 1.1 used to implement them.
So what does SOAP look like? Here is a sample SOAP envelope that uses the xmethods.net sample stock-quote SOAP service to implement the canonical SOAP RPC example of fetching the stock price for IBM (it’s the canonical example because it is the example from the SOAP proposal document):
<?xml version=”1.0” encoding=”UTF-8”?> <soap:Envelope
xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”

404 Chapter 16 RPC: Interacting with Remote Services
xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:soap-enc=”http://schemas.xmlsoap.org/soap/encoding/” soap:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”>
<soap:Body>
<getQuote xmlns=
“http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/”
>
<symbol xsi:type=”xsd:string”>ibm</symbol>
</getQuote>
</soap:Body>
</soap:Envelope>
This is the response:
<?xml version=”1.0” encoding=”UTF-8”?>
<soap:Envelope xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:soapenc=”http://schemas.xmlsoap.org/soap/encoding/” soap:encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/”>
<soap:Body>
<n:getQuoteResponse xmlns:n=”urn:xmethods-delayed-quotes”> <Result xsi:type=”xsd:float”>90.25</Result>
</n:getQuoteResponse>
</soap:Body>
</soap:Envelope>
SOAP is a perfect example of the fact that simple in concept does not always yield simple in implementation. A SOAP message consists of an envelope, which contains a header and a body. Everything in SOAP is namespaced, which in theory is a good thing, although it makes the XML hard to read.
The topmost node is Envelope, which is the container for the SOAP message.This element is in the xmlsoap namespace, as is indicated by its fully qualified name <soap:Envelope> and this namespace declaration:
xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/” which creates the association between soap and the namespace URI
http://schemas.xmlsoap.org/soap/envelope/.
SOAP and Schema
SOAP makes heavy implicit use of Schema, which is an XML-based language for defining and validating data structures. By convention, the full namespace for an element (for example, http:// schemas.xmlsoap.org/soap/envelope/) is a Schema document that describes the namespace. This is not necessary—the namespace need not even be a URL—but is done for completeness.


406 Chapter 16 RPC: Interacting with Remote Services
targetNamespace=
“http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/”
xmlns:tns=
“http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/” xmlns:electric=”http://www.themindelectric.com/” xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:soapenc=”http://schemas.xmlsoap.org/soap/encoding/” xmlns:wsdl=”http://schemas.xmlsoap.org/wsdl/” xmlns=”http://schemas.xmlsoap.org/wsdl/”>
<message name=”getQuoteResponse1”>
<part name=”Result” type=”xsd:float” /> </message>
<message name=”getQuoteRequest1”>
<part name=”symbol” type=”xsd:string” /> </message>
<portType name=”net.xmethods.services.stockquote.StockQuotePortType”> <operation name=”getQuote” parameterOrder=”symbol”>
<input message=”tns:getQuoteRequest1” /> <output message=”tns:getQuoteResponse1” />
</operation>
</portType>
<binding name=”net.xmethods.services.stockquote.StockQuoteBinding” type=”tns:net.xmethods.services.stockquote.StockQuotePortType”>
<soap:binding style=”rpc” transport=”http://schemas.xmlsoap.org/soap/http” /> <operation name=”getQuote”>
<soap:operation soapAction=”urn:xmethods-delayed-quotes#getQuote” /> <input>
<soap:body use=”encoded” namespace=”urn:xmethods-delayed-quotes” encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” />
</input>
<output>
<soap:body use=”encoded” namespace=”urn:xmethods-delayed-quotes” encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” />
</output>
</operation>
</binding>
<service name=”net.xmethods.services.stockquote.StockQuoteService”> <documentation>
net.xmethods.services.stockquote.StockQuote web service </documentation>
<port name=”net.xmethods.services.stockquote.StockQuotePort” binding=”tns:net.xmethods.services.stockquote.StockQuoteBinding”>
<soap:address location=”http://66.28.98.121:9090/soap” />
</port>
</service>
</definitions>

SOAP 407
WSDL clearly also engages in heavy use of namespaces and is organized somewhat out of logical order.
The first part of this code to note is the <portType> node. <portType> specifies the operations that can be performed and the messages they input and output. Here it defines getQuote, which takes getQuoteRequest1 and responds with getQuoteResponse1.
The <message> nodes for getQuoteResponse1 specify that it contains a single element Result of type float. Similarly, getQuoteRequest1 must contain a single element symbol of type string.
Next is the <binding> node. A binding is associated with <portType> via the type attribute, which matches the name of <portType>. Bindings specify the protocol and transport details (for example, any encoding specifications for including data in the SOAP body) but not actual addresses. A binding is associated with a single protocol, in this case HTTP, as specified by the following:
<soap:binding style=”rpc” transport=”http://schemas.xmlsoap.org/soap/http” />
Finally, the <service> node aggregates a group of ports and specifies addresses for them. Because in this example there is a single port, it is referenced and bound to http:/66.28.98.121:9090/soap with the following:
<port name=”net.xmethods.services.stockquote.StockQuotePort” binding=”tns:net.xmethods.services.stockquote.StockQuoteBinding”>
<soap:address location=”http://66.28.98.121:9090/soap” />
</port>
It’s worth noting that nothing binds SOAP to only working over HTTP, nor do responses have to be returned. SOAP is designed to be a flexible general-purpose messaging protocol, and RPC over HTTP is just one implementation.The WSDL file tells you what services are available and how and where to access them. SOAP then implements the request and response itself.
Fortunately, the PEAR SOAP classes handle almost all this work for you.To initiate a SOAP request, you first create a new SOAP_Client object and pass in the WSDL file for the services you want to access. SOAP_Client then generates all the necessary proxy code for requests to be executed directly, at least in the case where inputs are all simple Schema types.The following is a complete client request to the xmethods.net demo stock quote service:
require_once “SOAP/Client.php”;
$url = “http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl”; $soapclient = new SOAP_Client($url, true);
$price = $soapclient->getQuote(“ibm”)->deserializeBody(); print “Current price of IBM is $price\n”;
SOAP_Client does all the magic of creating a proxy object that allows for direct execution of methods specified in WSDL. After the call to getQuote() is made, the result is