XML and PHP [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

XML and PHP [Electronic resources] - نسخه متنی

Vikram Vaswani

نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
افزودن یادداشت
افزودن یادداشت جدید

PHP and RPC

A PHP module for RPC has been available since version 4.1.0 of the language (released in December 2001). Created by Dan Libby of Epinions.com, this RPC extension (which goes by the distressingly unpronounceable name of XMLRPC-EPI-PHP) provides standard encoding, decoding, and server administration functions for XML-RPC and SOAP requests and responses.This extension is also available as a standalone C library at the project's official web site: Appendix A, "Recompiling PHP to Add XML Support").

This chapter will focus exclusively on the XMLRPC-EPI-PHP extension. However, it should be noted that this RPC library is not the only game in townboth the XML-RPC web site (Chapter 8, "Open Source XML/PHP Alternatives."

Teething Trouble

It's important to note, at the outset itself, that some of the examples in this chapter (those related to PHP and SOAP) will not work with the released versions of PHP 4.1.0 and 4.1.1. This is because those versions of the language come with version 0.41 of the XMLRPC-EPI-PHP extension, which does not include support for SOAP (de)serialization.

The affected listings in this chapter are:

Listing 6.12 and its output, Listing 6.13

Listing 6.19

Listing 6.30 and Listing 6.31

In order to get these examples to work as advertised, you will need to upgrade to PHP 4.2.0 or better, which includes a newer version of the XMLRPC extension.

You might also like to take a look at SOAPx4, an alternative PHP-based SOAP implementation that's been discussed in Chapter 8, "Open Source XML/PHP Alternatives."

Note also that, as of this writing, the Windows version of PHP does not include an RPC extension, and therefore the examples in this chapter cannot be viewed in Windows. You will need a *NIX-based PHP build in order to try out the examples in this chapter.

Encoding and Decoding RPC Requests

PHP's RPC extension offers two functions designed specifically to encode RPC requests into XML.The first (and most commonly used) is the xmlrpc_encode_request() function that accepts, as function arguments, the name of the procedure to be called on the remote server and the parameters to be passed to it. Listing 6.8 demonstrates how it works.

Listing 6.8 Encoding a Remote Procedure Call with PHP

<?php 
// $numbers is an array of arguments to be passed to the remote procedure 
$numbers = array(34, 78, 2, 674); 
// encode procedure call 
echo xmlrpc_encode_request
("getRange", $numbers); 
?> 

The output of this code snippet is displayed in Listing 6.9.

Listing 6.9 An XML-RPC Request

<?xml version='1.0' encoding="iso-8859-1" ?> 
<methodCall> 
<methodName>getRange</methodName> 
<params> 
<param> 
<value> 
<int>34</int> 
</value> 
</param> 
<param> 
<value> 
<int>78</int> 
</value> 
</param> 
<param> 
<value> 
<int>2</int> 
</value> 
</param> 
<param> 
<value> 
<int>674</int> 
</value> 
</param> 
</params> 
</methodCall> 

The second argument passed to xmlrpc_encode_request() a variable containing arguments for the remote proceduremay be of any type supported by PHP, and it will automatically be encoded into a corresponding XML-RPC data type. Listing 6.10 demonstrates by encoding a PHP array of mixed type with xmlrpc_encode_request.

Listing 6.10 Encoding Different Data Types with PHP

<?php 
echo xmlrpc_encode_request
("some_remote_method", array("name" => "John Doe", 34, 
"abracadabra")); 
?> 

Listing 6.11 has the output.

Listing 6.11 An XML-RPC Request Containing Different Data Types

<?xml version='1.0' encoding="iso-8859-1" ?> 
<methodCall> 
<methodName>some_remote_method</methodName> 
<params> 
<param> 
<value> 
<struct> 
<member> 
<name>name</name> 
<value> 
<string>John Doe</string> 
</value> 
</member> 
<member> 
<name/> 
<value> 
<int>34</int> 
</value> 
</member> 
<member> 
<name/> 
<value> 
<string>abracadabra</string> 
</value> 
</member> 
</struct> 
</value> 
</param> 
</params> 
</methodCall> 

It's also possible to encode an RPC request as SOAP (rather than XML-RPC) data; this is accomplished by adding a third optional argument to the call to xmlrpc_encode_request() (see the sidebar entitled "What's in a Name?" for more output options). Listing 6.12 demonstrates how to encode a SOAP request.

Listing 6.12 Encoding a SOAP Request

<?php 
// $numbers is an array of arguments to be passed to the remote procedure 
$numbers = array(34, 78, 2, 674); 
// encode procedure call 
echo xmlrpc_encode_request
("getRange", $numbers, array("version" => "soap 1.1")); 
?> 

The output of this command is a SOAP envelope containing the remote procedure call and its arguments, as Listing 6.13 demonstrates.

Listing 6.13 A SOAP Request

<?xml version="1.0" encoding="iso-8859-1" ?> 
<SOAP-ENV:Envelope xmlns:SOAP-ENV=
"http://schemas.xmlsoap.org/soap/envelope/" xmlns:
xsi="http://www.w3.org/1999/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/1999/
XMLSchema" xmlns:SOAP-ENC="http://schemas
.xmlsoap.org/soap/encoding/" xmlns:si="http://
soapinterop.org/xsd" xmlns:ns6="http://testuri.org" 
SOAP-ENV:encodingStyle="http://
schemas.xmlsoap.org/soap/encoding/"> 
<SOAP-ENV:Body> 
<getRange> 
<xsd:int>34</xsd:int> 
<xsd:int>78</xsd:int> 
<xsd:int>2</xsd:int> 
<xsd:int>674</xsd:int> 
</getRange> 
</SOAP-ENV:Body> 
</SOAP-ENV:Envelope> 

Finding Fault

Wondering how to generate RPC faults on the server? Take a look at Listing 6.29.

It's also conceivable that you might need to generate XML for just a single procedure argument, rather than a complete RPC requestif, for example, you're incrementally building an RPC packet from a database or flat file. In this case, you can use the xmlrpc_encode() function, which returns an XML string containing the data-typed representation of the structure passed to xmlrpc_encode(). Listing 6.14 demonstrates encoding a single parameter.

Listing 6.14 Encoding a Single Parameter

<?php 
echo xmlrpc_encode(98.6); 
?> 

Listing 6.15 shows the output.

Listing 6.15 The XML-RPC Result of Encoding a Single Parameter

<?xml version="1.0" encoding="utf-8" ?> 
<params> 
<param> 
<value> 
<double>98.600000</double> 
</value> 
</param> 
</params> 

Buyer Beware!

As of this writing, PHP's RPC extension does not allow you to build an RPC packet incrementally, as is possible with the WDDX extension's wddx_add_vars() function.

This works with arrays, tooas Listings 6.16 and 6.17 demonstrate.

Listing 6.16 Encoding an Array

<?php 
$params = array("temperature" => "98.6", "convert_to" => "celsius"); 
// encode parameter 
echo xmlrpc_encode($params); 
?> 

Listing 6.17 The XML-RPC Result of Encoding an Array

<?xml version="1.0" encoding="utf-8" ?> 
<params> 
<param> 
<value> 
<struct> 
<member> 
<name>temperature</name> 
<value> 
<string>98.6</string> 
</value> 
</member> 
<member> 
<name>convert_to</name> 
<value> 
<string>celsius</string> 
</value> 
</member> 
</struct> 
</value> 
</param> 
</params> 

Once encoded, the RPC request needs merely to be transmitted to the RPC server. This transmission may take place using any supported protocol; in the case of the web, this is usually HTTP. For a full-fledged example, take a look at Listing 6.26.

Making the Connection

It's important to note that the RPC extension that ships with PHP merely provides encoding and decoding functions, and a server and introspection API. It does not address the very important issue of how the request will be transmitted across the network, and how the response will be retrieved.

As it is right now, developers need to manually implement a network transmission layer. Listing 6.29).

When the server receives the encoded RPC request, it needs to decode it, execute the procedure, obtain a return value, encode it as an RPC response, and transmit it back to the requesting client. At the server end of the connection, PHP's RPC extension automatically handles request decoding and response encoding via the xmlrpc_server_call_method() function (discussed in the next section); at the client end, however, the decoding of the server response needs to be implemented manually.

This is primarily accomplished by the xmlrpc_decode() function, a mirror of the xmlrpc_encode() function discussed in the preceding section. This function accepts an XML-RPC encoded response and decodes it into native PHP data types for subsequent use. Listing 6.18 demonstrates this.

Listing 6.18 Decoding an XML-RPC Response

<?php 
// sample response from server (XML-RPC) 
$response = <<< END 
<?xml version="1.0"?> 
<methodResponse> 
<params> 
<param> 
<value> 
<struct> 
<member> 
<name>start</name> 
<value> 
<int>2</int> 
</value>   
</member> 
<member> 
<name>end</name> 
<value> 
<int>674</int> 
</value> 
</member> 
</struct> 
</value> 
</param> 
</params> 
</methodResponse> 
END; 
// decode into native type and print 
$output = xmlrpc_decode($response); 
echo "Numbers provided fall in the range " .
 $output["start"] ." to " . $output["end"]; 
?> 

This works just as well with a SOAP-encoded response, too (see Listing 6.19).

Listing 6.19 Decoding a SOAP Response

<?php 
// sample response from server (SOAP) 
$response = <<< END 
<?xml version="1.0"?> 
<SOAP-ENV:Envelope xmlns:SOAP-ENV=
"http://schemas.xmlsoap.org/soap/envelope/" xmlns:
xsi="http://www.w3.org/1999/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/1999/
XMLSchema" xmlns:SOAP-ENC="http://schemas
.xmlsoap.org/soap/encoding/" xmlns:si="http://
soapinterop.org/xsd" xmlns:ns6="http://testuri.org" 
SOAP-ENV:encodingStyle="http://
schemas.xmlsoap.org/soap/encoding/"> 
<SOAP-ENV:Body> 
<getRangeResponse> 
<item> 
<start xsi:type="xsd:int">2</start> 
<end xsi:type="xsd:int">674</end> 
</item> 
</getRangeResponse> 
</SOAP-ENV:Body> 
</SOAP-ENV:Envelope> 
END; 
// decode into native type and print 
$output = xmlrpc_decode($response); 
echo "Numbers provided fall in the range " .
 $output["start"] ." to " . $output["end"]; 
?> 

The RPC extension also includes an xmlrpc_decode_request() function, which decodes an XML-encoded request string and returns the embedded remote procedure name and its arguments. It's unlikely that you'll ever need to use this function because (as you'll shortly see) the server can handle XML-encoded RPC requests without needing to decode them first, and the client will typically never need to decode a request (merely encode it). It's included with the library for the sake of completeness, and an example is included here for the same reason (see Listing 6.20).

Listing 6.20 Decoding an XML-RPC Request

<?php 
// encode a request 
$request = xmlrpc_encode_request
("someMethod", array("red", "green", "blue")); 
// decode it with xmlrpc_decode() 
// $params will contain the name of the called procedure 
// $output is an array containing passed parameters 
$params = xmlrpc_decode_request($request, &$method); 
// prints "Called method was: someMethod(red, green, blue)" 
echo "Called method was: $method(" . join(", ", $params) . ")"; 
?> 

Note that the variable containing the original remote procedure name must be passed to xmlrpc_decode_request() by reference.

Creating and Using an RPC Server

By itself, an RPC client is fairly useless; to be really valuable, it needs a server at the other end of the connection to process its requests and transmit information back to it. And so, PHP's RPC extension also provides a server API to support server instantiation and procedure registration and invocation.

There are a number of different steps involved here. So, let's look at a simple example to understand how the process works (see Listing 6.21).

Listing 6.21 A Simple PHP-based RPC Server

<?php 
// $request contains an XML-encoded request 
$request = <<< END 
<?xml version='1.0' encoding="iso-8859-1" ?> 
<methodCall> 
<methodName>getRange</methodName> 
<params> 
<param> 
<value> 
<int>34</int> 
</value> 
</param> 
<param> 
<value> 
<int>78</int> 
</value> 
</param> 
<param> 
<value> 
<int>2</int> 
</value> 
</param> 
<param> 
<value> 
<int>674</int> 
</value> 
</param> 
</params> 
</methodCall> 
END; 
// create server 
$rpc_server = xmlrpc_server_create() or die
("Could not create RPC server"); 
// register methods 
xmlrpc_server_register_method($rpc_server,
 "getRange", "phpGetRange") or die("Could not 
register method"); 
// call method 
$response = xmlrpc_server_call_method($rpc_server, $request, NULL); 
// print response 
echo $response; 
// clean up 
xmlrpc_server_destroy($rpc_server); 
// function to calculate highest 
// and lowest in a number series 
function phpGetRange ($method, $args, $add_args) 
{
sort ($args); 
return array ('start' => $args[0], 'end' => end ($args)); 
} 
?> 

Let's go through the process step-by-step:

Instantiating an RPC server is fairly simple: All it needs is a call to

xmlrpc_server_create(). 
$rpc_server = xmlrpc_server_create() or die
("Could not create RPC server"); 

This function returns a handle to the server, stored in the variable $rpc_server, which is required for all subsequent server operations.

After the server has been instantiated, the next step is to register methods or procedures with itthese are the same procedures that will be invoked by connecting clients. For this purpose, PHP offers the xmlrpc_server_register_method() function, which is used to register a PHP function to handle an exposed RPC procedure.

The xmlrpc_server_register_method() accepts three arguments: a handle to the RPC server, the name of the procedure (as exposed to connecting clients), and the name of the PHP function to call when that procedure is invoked.

xmlrpc_server_register_method
($rpc_server, "getRange", "phpGetRange") or die("Could not 
register method"); 

In Listing 6.21, the phpGetRange() function is called when a connecting client executes an RPC call to the getRange() procedure.

The xmlrpc_server_register_method() function can be used to register as many procedures as you like with the RPC server. The PHP functions registered via the xmlrpc_server_register_method() function must be capable of accepting three arguments: the name of the called procedure, the arguments passed to it, and any additional user-defined arguments.

function phpGetRange($method, $args, $add_args) 
{
... 
} 

With the remote procedure(s) registered with the XML-RPC server, the xmlrpc_server_call_method() function is used to parse and process the XML-encoded RPC request from the client. As Listing 6.21 demonstrates, this function requires three arguments: a handle to the RPC server, the XML-encoded request string, and any additional data that may be required by the procedure.

$response = xmlrpc_server_call_method
($rpc_server, $request, NULL); 
echo $response; 

In Listing 6.21, after the request is decoded by xmlrpc_server_call_method(), the phpGetRange() function is invoked. This function iterates through the number array passed to it to determine the highest and lowest in the series, and then returns an array containing these two values.

The xmlrpc_server_call_method() then encodes this return value as an XML document, for display or further processing. The return value may be encoded as either an XML-RPC response or a SOAP response (refer to the sidebar entitled "What's in a Name?" for more on how to do this).

After the remote procedure has been executed and a return value encoded into XML, the final step is to destroy the server and free up the memory it was occupying. This is accomplished via the aptly named xmlrpc_server_destroy() method.

xmlrpc_server_destroy($rpc_server); 

The previous steps make up a fairly standard process, and you'll see them being used over and over again in the examples that follow.

What's in a Name?

Both the xmlrpc_encode_request() and the xmlrpc_server_call_method() functions can accept one optional argument: an array containing information on the format of the resulting XML.

This array is an associative array containing any or all of the following key-value pairs. The default value for each key is marked in bold.

"output_type" => ("php" | "xml" ): specifies whether the function should return an XML document or a PHP data structure.

"verbosity" => ("white_space" | "newlines_only" | "pretty" ): specifies whether the XML should be returned as indented, multiline data, or a single-line string.

"escaping" => ("cdata" | "non-ascii" | "non-print" | "markup"): specifies if and how to escape special characters.

"version" => ("xmlrpc" | "soap 1.1" | "simple" | "auto" ): specifies the protocol to be used. Supported protocols include XML-RPC, SOAP 1.1, and a non-standard variant named simpleRPC. The "auto" value indicates that output should be sent in the same form as input.

"encoding" => ("iso-8859-1" | "utf-8" | ...): encoding to use for the output.

For example, the following code snippet generates an XML-RPC request as a single string:

<?php 
xmlrpc_encode_request($method, $params, array("version" => 
"xmlrpc", "verbosity" => "no_white_space")); 
?> 

Whereas, the following code snippet generates an indented, multiline SOAP response using UTF-8 encoding:

<?php 
xmlrpc_server_call_method($server, $request, $args, 
array("version" => "soap 1.1", "verbosity" => "pretty", 
"encoding" => "utf-8")); 
?> 

Note that SOAP support is not available in older versions of PHP's RPC extension. Refer to the earlier sidebar entitled "Teething Trouble" for more information on this.

Introspection

PHP's RPC extension also supports introspection, a fairly cool concept that allows your RPC server to display a certain degree of self-awareness about the procedures registered with it. A primitive introspection API makes it possible for developers to document the procedures they create, and provide this documentation to connecting clients using a simple XML vocabulary.

The Art of Self-Evaluation (Or, What the Heck Is Introspection?)

In a general sense, introspection refers to the capability of a piece of code to "discover" information about itself, and return this information when asked for it. So, for example, an object might be able to display a list of its exposed properties, together with information on what each one represents, or deliver a list of its public methods, together with the expected parameters and return values for each.

Obviously, this is not as magical or automatic as it sounds. The developer still needs to define these properties and describe them in a manner that can be used by an introspection API. However, by providing a simple description mechanism for an object's properties and methods, introspection makes it possible to analyze objects in a standard manner, automatically generate API documentation, and validate input parameters and output values.

In the long run, this reduces development times and makes code more maintainablewhich is why introspection is generally considered a Good Thing, and one that should be used whenever possible.

An example might make this clearer. Consider Listing 6.22, which invokes, over RPC, the procedure system.listMethods().

Listing 6.22 Listing Remote Procedures with system.listMethods()

<?php 
// a sample request 
$request = <<< END 
<?xml version="1.0"?> 
<methodCall> 
<methodName>system.listMethods</methodName> 
<params /> 
</methodCall> 
END; 
// create server 
$rpc_server = xmlrpc_server_create() or die
("Could not create RPC server"); 
// register methods 
// execute request 
$response = xmlrpc_server_call_method
($rpc_server, $request, NULL, array("version" => 
"xml")); 
// print response 
echo $response; 
// clean up 
xmlrpc_server_destroy($rpc_server); 
?> 

Now, system.listMethods() is a built-in function that generates a list of methods currently registered with the RPC server and returns this list to the client. Listing 6.23 demonstrates the response to the procedure call.

Listing 6.23 The Result of a Call to system.listMethods()

<?xml version='1.0' encoding="iso-8859-1" ?> 
<methodResponse> 
<params> 
<param> 
<value> 
<array> 
<data> 
<value><string>system.listMethods</string></value> 
<value><string>system.methodHelp</string></value> 
<value><string>system.methodSignature</string></value> 
<value><string>system.describeMethods</string></value> 
<value><string>system.multiCall</string></value> 
<value><string>system.getCapabilities</string></value> 
<value><string>getRandomQuote</string></value> 
</data> 
</array> 
</value> 
</param> 
</params> 
</methodResponse> 

This is a very basic example of introspectionthe server is aware of all the methods currently registered with it and can provide this information to clients on demand. Of course, this information is not limited to a mere list of procedure names. The system.describeMethods() procedure provides more exhaustive information on registered procedures, including details such as expected arguments and argument types, expected return values, optional and required arguments, related methods, bugs, and version information; whereas the system.methodHelp() procedure provides information on the purpose of each registered procedure. There are a few other procedures as well (take a look at the next sidebar entitled "Talking Back" for a list).

As an illustration, consider Listing 6.24, which demonstrates what system.methodHelp() has to say about the getRandomQuote() function.

Listing 6.24 Obtaining an XML Description of the getRandomQuote() Procedure with system.methodHelp()

<?xml version='1.0' encoding="iso-8859-1" ?> 
<methodResponse> 
<params> 
<param> 
<value><string>Retrieves a random quote by the specified 
author</string></value> 
</param> 
</params> 
</methodResponse> 

Talking Back

The system.listMethods() procedure is just one of a number of introspection functions provided by PHP's RPC extension. Here's a list of them all:

system.listMethods() Lists currently registered methods

system.describeMethods() Describes the available methods in detail

system.methodHelp() Returns documentation for a specified method

system.methodSignature() Returns signature for a specified method

system.getCapabilities() Lists server capabilities

Obviously, this information is not generated automatically; the developer needs to provide it to the RPC server while registering the corresponding procedure. For this purpose, PHP's RPC extension includes a function named xmlrpc_server_register_introspection_callback(), which names a callback function to handle introspection requests. Listing 6.25 has an example.

Listing 6.25 Registering an Introspection Handler

<?php 
xmlrpc_server_register_introspection_callback($rpc_server, 
"generateIntrospectionData"); 
?> 

In this case, the callback function generateIntrospectionData() is responsible for generating documentation in the approved XML vocabulary when it intercepts an introspection request. Listing 6.26 has a more detailed example.

Listing 6.26 A more Complete Introspection Handler

<?php 
// create server 
$rpc_server = xmlrpc_server_create() or die
("Could not create RPC server"); 
// register methods 
xmlrpc_server_register_method($rpc_server, 
"getRandomQuote", "phpGetRandomQuote") or 
die("Could not register method"); 
// function to run when introspection request comes through 
xmlrpc_server_register_introspection_
callback($rpc_server, "generateIntrospectionData"); 
// generate an XML-RPC request for system.methodHelp 
// this would normally come from an RPC client 
$request = xmlrpc_encode_request("system.methodHelp", "getRandomQuote"); 
// call method 
$response = xmlrpc_server_call_method
($rpc_server, $request, NULL, array("version" => 
"xml")); 
// print response 
echo $response; 
// clean up 
xmlrpc_server_destroy($rpc_server); 
// function to auto-generate documentation for procedures 
// (introspection) 
function generateIntrospectionData() 
{
$data = <<< END 
<?xml version="1.0"?> 
<introspection version="1.0"> 
<methodList> 
<methodDescription name="getRandomQuote"> 
<author>Vikram Vaswani</author> 
<purpose>Retrieves a random quote by the specified author</purpose> 
<version>1.0</version> 
<signatures> 
<signature> 
<params> 
<value type="string" name="author">The name of the quote author</
value> 
</params> 
<returns> 
<value type="string" name="quote">A quote by the specified author</
value> 
</returns> 
</signature> 
</signatures> 
<errors> 
<item>Returns fault code 2 if no quotes by the specified author are 
available</item> 
</errors> 
<notes /> 
<bugs/> 
<todo/> 
</methodDescription> 
</methodList> 
</introspection> 
END; 
return $data; 
} 
function phpGetRandomQuote($method, $args, $add_args) 
{
// code snipped out 
} 
?> 

Note that the XML-encoded documentation generated by the callback function must conform to the rules laid down in the introspection specification, available from the extension's official web site at http://xmlrpc-epi.sourceforge.net/, and that the function must actually exist for the introspection data to be correctly returned.

By and large, introspection is a good idea. It provides application developers with an efficient, transparent mechanism of obtaining API documentation, thereby making remote services more accessible and easier to usewhich, after all, is the holy grail of RPC. This book's web site includes links to a few web-based introspection clients that demonstrate this capability further.

/ 84