"Xenophobia" is an XML-RPC client and server library for PHP5. It conforms to the last revision of the specification and implements several extensions.
The primary goals of this library are ease of use (as little code as possible should be needed to interact with the library) and data integrity (well-formed and valid XML). All other concerns, including PHP compatibility, efficiency and extensibility are secondary.
Questions and comments are very welcome, and should be directed to the author via his Web site.
No other requirements are known to me. In limited testing PHP 5.0.0 appeared to crash the server; PHP 5.0.5 performed without upset.
Xenopohobia largely constitutes of a server class and a client class. Both need to be instantiated to be used.
XMLRPCServer XMLRPCServer::__construct ( void )
Once the server is instantiated it will have a number of "system" procedures, but will not serve incoming requests unless told to do so:
void XMLRPCServer::serve ( void )
At this point the server will process requests, but a server composed of only the basic system procedures doesn't do much. Procedure calls should first be added:
bool XMLRPCServer::addCall ( string name, callback callback [, array signature [, string help]] )
Above, name is an identifier for use by clients (eg. system.listMethods, math.sqRoot), callback is a callback as described in Ch.11, §10 of the PHP manual. Signature is a method signature as described by the original implementation: the array's first member is the return type; subsequent members are the types of required arguments. Finally, help is a human-readable, freeform description of the method.
Note that a method may have several signatures, but addCall
only accepts one. Additional signatures may be defined with addSignature
:
bool XMLRPCServer::addSignature ( string call, array signature )
If call is not yet defined, the method will return FALSE.
Below are some simple examples of server usage:
<?php // Implementing the familiar PHP "implode" function as an RPC $server = new XMLRPCServer(); $server->addCall("string.concat", "implode", array("string", "array"), "Takes an array of strings (optionally with a 'glue' string) and returns a concatenation."); $server->addSignature("string.concat", array("string", "string", "array")); $server->serve();
<?php // Implementing something similar to the above // First have a callback, in this case a function function xmlrpc_concat() {$args = func_get_args(); if (sizeof($args)==1 && is_array($args[0])) {return implode("", $args[0]);} elseif (sizeof($args) < 2) {return new XMLRPCException("No strings to concatenate", 0);} else {return implode("", $args);} } //Now start the server $server = new XMLRPCServer(); $server->addCall("string.concat", "xmlrpc_concat", array("string", "array"), "Takes an array of strings or a variable number of strings and concatenates them."); $server->addSignature("string.concat", array("string", "string", "string")); $server->serve();
The Xenophobia server sends its reply by outputting as any other PHP script with echo
. Because of this, printed error messages will usually cause the server to send malformed XML to clients.
To prevent this, one may set PHP to report no errors (by using error_reporting(0)
), but this has the disadvantage of giving no indication when an unforeseen, possibly serious problem has occured with the server. Xenophobia provides a more elegant solution: the static method XMLRPCServer::handleErrors()
.
This method, which can be called as soon as the library is included, reports any errors or uncaught exceptions that would normally be printed as an XML-RPC fault. Note, however, that when using this method all errors within the error-reporting threshold are considered fatal. Parse errors and the like, of course, cannot be caught by this facility.
Below is a simplistic example of how this works:
<?php XMLRPCServer::handleErrors(); //hand off error handling $document = DOMDocument::loadXML("<malformedXML>"); //try to parse bad XML; this would print warnings // The server will respond with this message (formatted here for readability): /* <?xml version="1.0"?> <!-- This message is an XML-RPC server response, generated due to an error in the script automating this server. If you were expecting something other than an XML-RPC response, the error is likely beyond your control. --> <methodResponse><fault><value> <struct> <member> <name>faultCode</name> <value><int>-32500</int></value> </member> <member> <name>faultString</name> <value><string>DOMDocument::loadXML(): Premature end of data in tag malformedXML line 1 in Entity, line: 1 (PHP code: 2)</string></value> </member> </struct> </value></fault></methodResponse> */
Server capabilities (which can be enumerated with the procedure call system.getCapabilities
) are an advisory list of what the server can do. Typically, this is a list of specifications which the given server adheres to. The list of capabilities for a base Xenophobia server is provided in brief in the preface.
To add a capability, the method XMLRPCServer::addCapability()
is used:
bool XMLRPCServer::addCapability ( string name, string spec_url, int spec_version )
name is an arbitrary name for the capability, sometimes provided by specifications, but often not. spec_url is a URL to the specification of the given capability, and spec_version is an integer version number for the specification; ideally this should be a revision date in YYYYMMDD form, but exact revision dates are sometimes difficult to ascertain.
Herewith an example, adding a capability for "Pingback":
<?php $server = new XMLRPCServer(); $server->addCapability("pingback", "http://www.hixie.ch/specs/pingback/pingback", 20070114); $server->serve();
As a convenience to both users and authors and as a reasonably complete test of the Xenophobia library, HTTP GET requests to a Xenophobia server will present a nicely-formatted HTML document containing a list of capabilities and procedure calls implemented by that particular server.
This list acts as a fairly complete test of the library since the server queries itself with a client: all data about the server is retrieve via RPCs, and the following functions are tested:
system.getCapabilities
system.listMethods
system.methodSignature
and system.methodHelp
multiple times using system.multicall
As this document is presented once XMLRPCServer::serve()
is called, the list of capabilities and procedure calls should always be accurate.
Using the Xenophobia client is quite simple. First a client instance must be created:
XMLRPCClient XMLRPCClient::__construct ( string server_url )
The server_url must be an absolute URL to an XML-RPC server. Explicit credentials or port number are allowed.
Once a server has been specified the client can then perform an unlimited number of procedure calls with the XMLRPCClient::call()
method:
mixed XMLRPCClient::call ( string call [, mixed parameter... ] )
When using this method, the call and zero or more parameters are sent to the server for processing. The server's reply is then returned, whether it be the expected result or an XML-RPC fault.
It is the responsibility of the user to ensure that the data type of each parameter matches what the server expects. Although the Xenophobia server will gladly accept values that are of a different type thanks to PHP's type juggling, other servers may fail in this case.
Below is an example of the client's use.
<?php // retrieve the current time from UserLand's time server $client = new XMLRPCClient("http://time.xml-rpc.com/RPC2"); $result = $client->call("currentTime.getCurrentTime"); //returns a "DateTime" object echo $result->format("r"); /* prints, for example: Fri, 02 Feb 2007 14:49:17 -0500 */
system.multicall
is a procedure call that allows a client to perform an arbitrary number of calls with a single request. This has the advantage of reducing HTTP traffic, and cutting down the time and processing power needed to perform multiple requests.
Xenophobia implements system.multicall
thus: First, procedure calls must be added to a queue:
bool XMLRPCClient::addCall ( string call [, mixed parameter...] )
Once all desired calls are queued, the multicall itself is executed:
array XMLRPCClient::call ( void )
Returned is an array of values, one for each call in the queue. If the XMLRPCClient::call()
method is passed parameters while there are calls in the queue, the specified call bypasses the queue entirely and is executed immediately.
The queue is cleared whenever a multicall is performed, but it can also be cleared explicitly:
void XMLRPCClient::clear ( void )
Note that system.multicall
can also be used like any other procedure call. This is especially useful to test whether a server implements it.
Xenophobia has a small number of public properties used for configuration purposes. Of these, only two---both static properties---are not properties of the XMLRPCServer
or XMLRPCClient
classes:
nil
element when sending messages. This property is set to FALSE by default; in this case the library will use an empty value
element in its messages. Note that neither of these two behaviours are standard: other clients and servers may refuse messages containing either implicit or explicit NULL values.
Most data types defined by the XML-RPC specification have direct mappings to PHP types, and conversion between these types is handled automatically by Xenophobia both for the server and the client. There are, however, a few notable peculiarities in the conversion between some types, and others simply have no equivalency. These limitations and peculiarities are documented here.
There are also tables summarizing type mappings in Appendix A.
XML-RPC faults are somewhat analogous to PHP exceptions; consequently faults are implemented as exceptions via the class XMLRPCException
. This class functions exactly as a typical exception would.
When a procedure call on the server needs to return a fault, an XMLRPCException
instance can be either returned or thrown: in either case the server will capture it and convert it to the proper XML structure. When the client receives or produces a fault, it will be returned; exceptions are thrown only if the client is configured to do so. See §2.3 for details.
XML-RPC has two compound types: arrays and structs. Unlike in PHP, XML-RPC arrays are only a collection of values; no keys are stored. Instead, structs are used when key preservation is desired. This, however, presents a problem when converting a PHP array: is it an array or a struct? Xenophobia answers this question using a fairly simple algorithm:
If every key, after being cast as an integer, is loosely equivalent to the original key, the array is an XML-RPC array; otherwise it is a struct. Whether the numeric keys are contiguous or even in order is irrelevant: the array is simply iterated with foreach
and each key is compared. The values are then constructed into an XML-RPC array in the order in which they were found. Is it up to the user to sort array keys beforehand if desired.
Note also that an empty array is considered an XML-RPC array. If an empty struct is required, an object with no public properties should be used instead, as objects are always converted into structs.
Although a PHP object is considered to be equivalent to a struct, an XML-RPC struct is always converted into an associative array.
XML-RPC has no null type. As such, using NULL when formulating a request or response should be avoided.
Nevertheless Xenophobia is capable of dealing with implicit null values and the non-standard explicit nil
element. A value in an XML-RPC message is considered to be null if the value
element contains no child elements or contains a nil
element. No other conditions will produce NULL. Unknown values in an XML-RPC message produce an XMLRPCUnknownValue
object.
When constructing a message, NULL will be represented by an empty value
element unless the library is configured to send nil
elements. See §2.1 for details. PHP types with no XML-RPC analogue (such as resources) produce an empty string
element.
PHP has no native type for dates as such, but PHP 5.2 introduces a DateTime
class, which Xenophobia uses in lieu of something else. When running on versions of PHP prior to 5.2, the DateTime
class is emulated with a PHP class that replicates the same basic functionality. The companion DateTimeZone
class is not emulated, since XML-RPC is ambiguous as to timezone handling.
Unfortunately the PHP manual has, as of this writing, no useful documentation on the DateTime
class---probably because it is still primitive and inconsistent. Worse, what documentation there is is apparently inaccurate. Therefore, the class should probably only be used for extracting the current date or time with the format
method. Still, since there are no better options, below is a table summarizing the emulated class' methods. It has no public properties.
Method | Notes |
---|---|
DateTime DateTime::__construct ( [string time [, DateTimeZone timezone]] ) | time is of the format accepted by strtotime ; timezone is ignored
|
string DateTime::format ( string format ) | Analogous to the date function
|
int DateTime::getOffset ( void ) | Returns timezone offset in seconds |
DateTimeZone DateTime::getTimezone ( void ) | This method is a no-op |
void DateTime::modify ( string modification ) | modification is of the format accepted by strtotime
|
void DateTime::setDate ( int year, int month, int day ) | |
void DateTime::setISODate ( int year, int week [, int day] ) | |
void DateTime::setTime ( int hour, int minute [, int second] ) | |
void DateTime::setTimezone ( DateTimeZone timezone ) | This method is a no-op |
Although PHP has adequate facilities for dealing with base64-encoded strings, there is no way to disambiguate between a string that is encoded and one that is not. Therefore, when wishing to send a base64-encoded string in a request or response, one must use the XMLRPCBase64
class directly:
XMLRPCBase64 XMLRPCBase64::__construct ( string data )
Base64-encoded data received in a message is automatically decoded and provided as a string, however.
PHP | Intermediary class | XML-RPC |
---|---|---|
Notes:
| ||
Null | XMLRPCNull | nothing or <nil> [1] |
String | XMLRPCString | <string> |
Integer | XMLRPCInt | <int> |
Float | XMLRPCFloat | <double> |
Boolean | XMLRPCBoolean | <boolean> |
Array | XMLRPCArray or XMLRPCStruct [2] | <array> or <struct> [2] |
new DateTime() [3] | XMLRPCDate | <dateTime.iso8601> |
Object | XMLRPCStruct | <struct> |
XMLRPCBase64 | <base64> | |
XMLRPCException [4] | <fault> | |
(anything else) | XMLRPCUnknownValue | empty <string> |
XML-RPC | Intermediary class | PHP |
---|---|---|
Notes:
| ||
nothing or <nil> [1] | XMLRPCNull | Null |
<string> | XMLRPCString | String |
<int> or <i4> | XMLRPCInt | Integer |
<double> | XMLRPCFloat | Float |
<boolean> | XMLRPCBoolean | Boolean |
<array> | XMLRPCArray | Array |
<struct> | XMLRPCStruct | Array [2] |
<dateTime.iso8601> | XMLRPCDate | new DateTime() [3] |
<base64> | XMLRPCBase64 | String [4] |
<fault> | XMLRPCException [5] | |
(anything else) | XMLRPCUnknownValue |
Class | arguments |
---|---|
XMLRPCNull | ( void ) |
XMLRPCString | ( [string string] ) |
XMLRPCInt | ( int integer ) |
XMLRPCFloat | ( float float ) |
XMLRPCBoolean | ( bool boolean ) |
XMLRPCArray | ( mixed members ) |
XMLRPCStruct | ( mixed members ) |
XMLRPCDate | ( mixed datetime ) |
XMLRPCBase64 | ( string data ) |
XMLRPCException | ( [string message [, int code]] ) |
XMLRPCUnknownValue | ( void ) |
Below is the RELAX NG schema (in compact form) used by Xenophobia to ensure data integrity. It makes allowances for both the explicit <nil> element and implicit null values.
grammar { start = (element methodCall { Method & CallParams? } | element methodResponse { RespParams | Fault }) Method = element methodName { text } CallParams = element params { element param { Value }* } RespParams = element params { element param { Value } } Value = element value { ValueInt | ValueDouble | ValueBoolean | ValueString | ValueBase64 | ValueDate | ValueArray | ValueStruct | ValueNullImplicit | ValueNullExplicit } ValueInt = (element int { text } | element i4 { text }) ValueDouble = element double { text } ValueBoolean = element boolean { "1" | "0" } ValueString = element string { text } ValueBase64 = element base64 { text } ValueDate = element dateTime.iso8601 { text } ValueArray = element array { element data { Value* }? } ValueStruct = element struct { element member { element name { text }& Value }* } ValueNullImplicit = empty ValueNullExplicit = element nil { empty } Fault = element fault { element value { element struct { element member { element name { "faultCode" }& element value { ValueInt } }, element member { element name { "faultString" }& element value { ValueString } } } } } }
Xenophobia should be fairly easy to extend. Though it was not designed with user extensibility in mind, it was designed to be as modular as practical for its feature-set.
System procedures implemented by the server are defined just as any other procedure call (ie. with XMLRPCServer::addCall()
) and are handled no differently from user-defined calls.
Types, unfortunately, are somewhat more difficult. Although all types implement the same interface (XMLRPCValue
) and conversion from the intermediary types to DOM nodes or native types is self-contained, conversion to these intermediary types from native types is handled by the static method XMLRPCLib::nativeToRPC()
. Furthermore, conversion from DOM nodes bypasses the intermediary classes, producing native values directly with XMLRPCLib::nodeToNative()
. Both these methods would need to be edited to add types.
To complicate matters further, the list of types in the RELAX NG schema is, naturally, rigid. This would require either the schema to be edited or schema validation to be disabled.
Xenophobia and its manual (ie. this document) were written by J. King. They are both licensed under the Creative Commons Attribution license (v2.5).
The server self-test stylesheet and this manual's stylesheet were written by Dustin Wilson. They are likewise both licensed under the Creative Commons Attribution license (v2.5).
The RELAX NG schema used in Xenophobia was generated from the compact schema in Appendix B using Trang and then compacted using a pattern replacement.
Thanks go out to the aforementioned Dustin Wilson for his sense of style and years of friendship. An uneasy thanks also goes to The Internet Archive for having archive copies of all the XML-RPC extension specifications I know about: half of them no longer exist at their original locations.