Xenophobia XML-RPC library manual

Preface

"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.

Download current version (archives)

Table of contents

Prefaces

  1. Preface
  2. Table of contents
  3. Features
  4. Requirements

Body

  1. Using the library
    1. Using the server
      1. Catching exceptions and errors
      2. Adding capabilities
      3. Server self-test
    2. Using the client
      1. Multicall
  2. Configuration properties
    1. Library properties
    2. Server properties
    3. Client properties
  3. Type conversion
    1. Fault handling
    2. Array disambiguation
    3. Null handling
    4. Datetime handling
    5. Base64-encoded data

Appendices

  1. Type conversion tables
  2. RELAX NG schema
  3. Extending the library
  4. Credits and licensing
  5. Revision history

Features

Conformance

Integrity

Usability

Requirements

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.

Using the library

Xenopohobia largely constitutes of a server class and a client class. Both need to be instantiated to be used.

Using the server

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();

Catching exceptions and errors

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>
*/

Adding capabilities

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();

Server self-test

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:

As this document is presented once XMLRPCServer::serve() is called, the list of capabilities and procedure calls should always be accurate.

Using the client

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

*/

Multicall

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.

Configuration properties

Library properties

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:

XMLRPCLib::$useNil
This static property is used by both the server and the client to decide whether to use the explicit 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.
XMLRPCLib::$schema
This static property contains the RELAX NG schema (in XML form) used by Xenophobia when validating messages.

Server properties

XMLRPCServer::$useSchema
This property sets whether the server should validate incoming messages using the library's RELAX NG schema. It defaults to TRUE.
XMLRPCServer::$inputSource
This property defines the stream URI from which the server will pull the incoming message. Set to php://input by default, it should be changed only for debugging purposes. Changing this property disables both the server's GET self-test and Content-Type checking. See also Appendix M of the PHP manual for information on possible values.

Client properties

XMLRPCClient::$useSchema
This property sets whether the client should validate incoming messages using the library's RELAX NG schema. It defaults to TRUE.
XMLRPCClient::$useCurl
This property sets whether the clint should should try using cURL (if available) to send its messages before falling back to using the HTTP stream wrapper. It is TRUE by default.
XMLRPCClient::$debug
This property sets the client in debug mode. In this mode any call will return, instead of the response data, an array with the keys request and response, which will contain the serialized XML data sent to and received from the server respectively. No validation will be done on the response. If using cURL the HTTP header is included with the reponse. This property is set to FALSE by default.
XMLRPCClient::$throw
This property sets whether the client will throw XML-RPC faults as exceptions instead of returning them as with other values. The default is FALSE.

Type conversion

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.

Fault handling

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.

Array disambiguation

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.

Null handling

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.

Datetime handling

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.

DateTime class methods
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

Base64-encoded data

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.

Type conversion tables

Type conversion: PHP to XML-RPC
PHP Intermediary class XML-RPC
Notes:
  1. XML-RPC has no "null" type. A non-standard extension introduces a <nil> element, but the library does not make use of it by default. See §2.1 for details.
  2. Empty arrays or arrays where all keys are integer-like (eg. 2, "3") are converted to XML-RPC arrays. Anything else is converted to a struct. If an empty struct is desired, use an object rather than an array.
  3. PHP 5.2 introduces a predefined DateTime class. For earlier versions of PHP a close approximation is emulated.
  4. XMLRPCExceptions can be either thrown or returned by the server with the same effect. Faults received by the client are returned, but can be thrown if desired. See §2.3 for details.
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>
Type conversion: XML-RPC to PHP
XML-RPC Intermediary class PHP
Notes:
  1. XML-RPC has no "null" type. A non-standard extension introduces a <nil> element, but the library does not make use of it by default. See §2.1 for details.
  2. Array keys in struct arrays are preserved.
  3. PHP 5.2 introduces a predefined DateTime class. For earlier versions of PHP a close approximation is emulated.
  4. The resultant string is decoded, of course.
  5. Faults received by the client are returned, but can be thrown if desired. See §2.3 for details.
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
XML-RPC type constructor usage
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 )

RELAX NG schema

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 } }
   }
  }
 }
}

Extending the library

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.

Credits and licensing

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.

Revision history

2009-04-01
Several changes:
2007-02-07
Fixed error in self-test HTTP header. Oops.
2007-02-05
First release.