ÄúµÄλÖãºÑ°ÃÎÍøÊ×Ò³£¾±à³ÌÀÖÔ°£¾PHP ±à³Ì£¾PHP 5 Power programming
Team LiB
Previous Section Next Section

8.7. Communicating with XML

Applications currently communicate via the Internet in several ways, most of which you already know. TCP/IP and UDP/IP are used, but are only low-level transport protocols. Communication between systems is difficult because systems store data in memory using different methods. For example, Intel has a different order of data in memory (Little Endian) than PowerPCs (Big Endian). Another major point was that people just wanted a solid cross-platform technology communication system. One solution is RPC (Remote Procedure Calls), but it's not easy to use, and it's implemented differently by Windows than by most UNIX platforms. XML is often the best solution. XML was developed to "promote" interoperability between systems. It allows applications on different systems to communicate using a standard format. XML is ASCII data, so the differences between systems (such as Endianess) is minimized. Other differences, such as date representation, still exist. One platform might specify Wed Dec 25 16:58:40 CET 2002, another just Wed 2002-12-25. XML-RPC and SOAP are both XML-based protocols. SOAP is the broader protocol, designed specifically for communication, and is well-supported.

8.7.1. XML-RPC

Let's start with the simplest way of communication: XML-RPC.

8.7.1.1 Messages

XML-RPC is a request-response protocol. For every request to a server, a response is returned. The response can be a valid result or an error. Both the request and response packets are encoded as XML. The values in the packets are encoded with different elements. The XML-RPC specification defines a number of scalar types to which the data that is going to be transported must be converted (see Table 8.1).

Table 8.1. XML-RPC Data Types

XML-RPC Type

Description

Example Value

<i4 /> or <int />

Four-byte signed integer

-8123

<boolean />

0 (false) or 1 (true)

1

<string />

ASCII string

Hello world

<double />

Double-precision signed floating-point number

91.213

<dateTime.iso8601 />

Date/time

200404021T14:08:55

<base64 />

Base 64-encoded binary

eW91IGNhbid0IHJlYWQgdGhpcyE


When a value is transported, it is wrapped inside a <value /> tag, like this:

<value><dateTime.iso8601 />20021221R14:12:81</dateTime.iso8601> <value>

Two compound data types are available: <array /> for non-associative arrays, and <struct /> for associative arrays. Here is an example of an <array />:

<array>
 <data>
  <value><int>1</int></value>
  <value><string>Hello!</string</value>
 </data>
</array>

As you can see, the values 1 and Hello! are wrapped into the <data /> element, which is a child of the <array /> element. In addition, <struct /> elements have a key associated with a value, so the XML looks slightly more complicated:

<struct>
 <member>
  <name>key-een</name>
  <value><int>1</int></value>
 </member>
 <member>
  <name>key-zwei</name>
  <value><int>2</int></value>
 </member>
</struct>

The values (both scalar and compound) are wrapped inside special tags in requests and responses, which you can see in the following sections.

8.7.1.2 Request

Requests in XML-RPC are normal POST requests to an HTTP server with some special additions:

POST /chapter_14/xmlrpc_example.php HTTP/1.0
User-Agent: PHP XMLRPC 1.0
Host: localhost
Content-Type: text/xml

The Content-Type is always text/xml.

Content-Length: 164

<?xml version="1.0"?>

Next, an XML declaration appears. The body consists solely of an XML document, as follows:

<methodCall>
 <methodName>hello</methodName>
 <params>
  <param>
   <value><string>Derick</string></value>
  </param>
</params>
</methodCall>

Every RPC request call consists of the <methodCall /> tag, followed by the <methodName /> tag that specifies the name of the remote function to call. Parameters can be passed. Each parameter is passed inside a <param /> element. The param elements are grouped and enclosed in the <params /> element, a child of the <methodCall /> element. The XML-RPC packet in the previous example code calls the remote "hello" function, passing the parameter Derick.

8.7.1.3 Response

When the function call succeeds, an XML-RPC response is returned to the caller program, encoded in XML. There are basically two different responses possible to a request: a normal response (methodResponse), shown in the following example, or a fault.

You can recognize a normal response by the <params /> child element of the <methodReponse /> tag. A successful methodResponse always has one <params /> child, which always has one <param /> child. You can't return more than one value from within a function, but you can return a <struct /> or an <array /> to mimic returning multiple values. The methodResponse shows the result of the request shown in the previous section:

<?xml version="1.0"?>
<methodResponse>
 <params>
  <param>
   <value><string>Hi Derick!</string></value>
  </param>
 </params>
</methodResponse>

8.7.1.4 Fault

Not all requests return a normal response, and not everything works as expected (for example, if the PEBCAK). When something doesn't work as expected, a <fault /> element is returned, rather than a <params /> element. The <fault /> always contains a <struct /> with two members: the faultCode (with an integer value) and a faultString (a string). Because the faultCodes are not defined in the XML-RPC specification, they are implementation-independent.

Here is an example of a <fault /> response:

<?xml version="1.0"?>
<methodResponse>
<fault>
  <value>
    <struct>
      <member>
        <name>faultCode</name>
        <value><int>3</int></value>
      </member>
      <member>
        <name>faultString</name>
        <value><string>Incorrect parameters passed to method<string></value>
      </member>
    </struct>
  </value>
</fault>
</methodResponse>

8.7.1.5 The Client

Now, it's time for a practical application. We'll start by writing a simple client to call XML-RPC functions on our local machine (a sample for the server follows in the next section). We will be using the PEAR class "XML_RPC", which can be installed with pear install XML_RPC:

<?php
    require_once "XML/RPC.php";

    $client = new XML_RPC_Client('/chap_14/xmlrpc_example.php',
      'localhost');

The script starts by including the PEAR class and instantiating an XML_RPC_Client object, as shown. The first parameter in the constructor is the path to the XML-RPC server on the "remote" machine; the second one is the hostname of that machine. Next, we continue by writing a small utility method that calls the method through the XML_RPC_client object. The function checks whether a fault is returned and if so, prints the accompanying error message. If a fault is not returned, the value that was returned by the RPC function is printed.

function call_method (&$client, &$msg)
{
        /* Send the request */
        $p = $client->send($msg);
        /* Check for an error, and print out the error message if
         * necessary */
        if (PEAR::isError($p)) {
              echo $p->getMessage();
          }else {
                /* Check if an XML RPC fault was returned, and display
                 * the faultString */
                if ($p->faultCode()) {
                       print $p->faultString();
                       return NULL;
                } else {
                     /* Return the value upon a valid response */
                     $res = $p->value();
                     return $res;
                }
        }
}

Next, we call the RPC functions via the function written. We can specify types for the parameters that we pass to the remote function either explicitly or implicitly. In this first example, we construct an XML_RPC_Message with one explicit parameter that has the value 'Derick' and the type 'string'. The function we call is 'hello', and won't do much more than return hi in response.

/* Construct the parameter array */
$vals = array (
      new XML_RPC_Value('Derick', 'string')
);

/* Construct the message with the functionname and
 * the parameter array */
$msg = new XML_RPC_Message('hello', $vals);

/* Send the message and store the result in $res */
$res = call_method($client, $msg);

/* If the result is non-null, decode the XML_RPC_Value into a PHP
 * variable and echo it (we assume here that it returns a
 * string */
if ($res !== NULL) {
       echo XML_RPC_decode($res)."\n";
}

Rather than instantiating an XML_RPC_Value object with an explicit value type, you can call XML_RPC_encode(<value>), which examines the type of the PHP variable and encodes it as the best-fitting XML-RPC type. Table 8.2 shows the type conversions.

Table 8.2. PHP Type to XML RPC Type Mappings

PHP Type

XML RPC Type

NULL

<string> (empty)

Boolean

<boolean>

String

<string>

Integer

<int>

Float

<double>

Array (non-associative)

<struct>

Array (associative)

<struct>


Notice that XML-RPC doesn't have a NULL type and that all types of arrays are converted to a <struct> (because it is inefficient to determine if a PHP array has only numeric indices).

The following example passes two <double>s to the 'add' function, which adds the two numbers and returns the result:

/* Somewhat more example with explicit types and multiple
 * parameters */
$vals = array (
      XML_RPC_encode(80.9),
      XML_RPC_encode(-9.71)
);
$msg = new XML_RPC_Message('add', $vals);
$res = call_method($client, $msg);
echo XML_RPC_decode($res)."\n";

The XML_RPC_decode() function does exactly the opposite of the XML_RPC_encode() function. Types convert from XML-RPC types to PHP types as shown in Table 8.3.

Table 8.3. XML RPC Types to PHP Type Mappings

XML-RPC Type

PHP Type

<i4> or <int>

Integer

<Boolean>

Boolean

<string>

String

<double>

Float

<dateTime.iso8601>

String (20040416T18:16:18)

<base64>

String

<array>

Array

<struct>

Array


8.7.1.6 Retrospection

If you encountered an XML-RPC server somewhere on the Internet, you might want to know which functions it exports. XML-RPC provides support functions that help you to retrieve all the information necessary to call the functions on the server. This is called retrospection. With the 'system.listMethods' function, you can retrieve an array containing all exported functions:

/* Complex example which shows retrospection */
$msg = new XML_RPC_Message('system.listMethods');
$res = call_method($client, $msg);

foreach (XML_RPC_decode($res) as $item) {

By looping through the returned array, you can request additional information on each function: the description of the function (with the system.methodHelp function) and the signature of the function (with system.methodSignature). system.methodHelp returns a string containing the description. system.methodSignature returns an array of arrays containing the types of the parameters. The first element in the array is the return type; the remaining elements contain the types of the parameters to pass to the function. The following code first requests the description, and then the types of the return value and parameters for the function:

       $vals = array (XML_RPC_encode($item));
       $msg = new XML_RPC_Message('system.methodHelp', $vals);
       $desc = XML_RPC_decode(call_method($client, $msg));

       $msg = new XML_RPC_Message('system.methodSignature', $vals);
       $sigs = XML_RPC_decode(call_method($client, $msg));
       $siginfo = '';
       foreach ($sigs[0] as $sig) {
           $siginfo .= $sig. " ";
       }

       echo "$item\n". wordwrap($desc). "\n\t$siginfo\n\n";
    }

?>

This was the client side. Now, let's implement the server side of our two functions.

8.7.1.7 The Server

Writing the server is not much harder than writing the client. Instead of including the XML/RPC.php file, we now include the file that implements the server functionality:

<?php
    require("XML/RPC/Server.php");

Next, we implement the functions themselves:

function hello ($args)
{
        /* The getValues() method returns an array with all
         * parameters passed to the function, converted from
         * XML RPC types to PHP types with the
         * XML_RPC_decode() function */
        $vals = $args->getValues();

        /* We simply return an XML_RPC_Values containing the
        * result with the 'string' type */return new XML_RPC_Response(
             new XML_RPC_Value("Hi {$vals[0]}!", 'string')
    );
}

function add ($args) {
       $vals = $args->getValues();
       return new XML_RPC_Response(
             new XML_RPC_Value($vals[0] + $vals[1], 'double')
    );
}

To make the functions available to the outside, we need to define the methods by putting the function name, signature, and description string into an array containing an element for each function. The signature is formatted as how the system.methodSignature should return itan array with an array containing the types:

$methods = array(
       'hello' => array (
             'function'  => 'hello',
             'signature' => array(
                  array(
                      $GLOBALS['XML_RPC_String'],
                      $GLOBALS['XML_RPC_String']
                  )
             ),
             'docstring' => 'Greets you.'
       ),

       'add' => array (
             'function'  => 'add',
             'signature' => array(
                  array(
                      $GLOBALS['XML_RPC_Double'],
                      $GLOBALS['XML_RPC_Double'],
                      $GLOBALS['XML_RPC_Double']
                  )
             ),
             'docstring' => 'Adds two numbers'
    )
);

We make the defined methods available by instantiating the XML_RPC_Server class. The constructor of this class handles parsing the request and calling the functions. You need to do nothing on your own, unless you want more advanced features that fall outside of the scope of this chapter.

    $server = new XML_RPC_Server($methods);
?>

With this, we conclude XML-RPC.

8.7.2. SOAP

This section guides you through using SOAP as a client for the Google Web API and implementing your own SOAP server. Because SOAP is even more complex than XML-RPC, we unfortunately can't include everything.

8.7.2.1 PEAR::SOAP

Google is a nice, fast search engine. Wouldn't it be great to have your own command-line search engine written in PHP? This section tells you how.

Google

To make use of the SOAP API that Google exports, you need an account, which you can create on http://www.google.com/apis/. When you register, you receive a key via email that you use when you call the SOAP method. For the following example to work correctly, you need to install the PEAR SOAP class, with pear install SOAP. After SOAP is installed, we can start with the following simple script. First, include the PEAR::SOAP class:

#!/usr/local/bin/php
<?php
     /* Include the class */
     require_once 'SOAP/Client.php';

Next, we define the URL to the SOAP server and instantiate a SOAP_Client object, which we will use to execute our search:

/* Create the client object */
$endpoint = 'http://api.google.com/search/beta2';
$client = new SOAP_Client($endpoint);

The search string is passed on the command line. If no parameter was passed, we'll display a little usage message:

/* Read the search string from the command line */
if ($argc != 2) {
      echo "usage: ./google.php searchstring\n\n";
      exit();
}
$query = $argv[1];

Then, we set up the other parameters for the SOAP call. Note that we don't do anything to specify the type of the variables; we just let the class decide this for us:

/* Defining the 'license' key */
$key = 'jx+PnvxQFHIrV1A2rnckQn8t91Pp/6Zg';

/* Defining maximum number of results and starting index */
$maxResults = 3;
$start = 0;

/* Setup the other parameters */
$filter = FALSE;
$restrict = '';
$safeSearch = FALSE;
$lr = '';
$ie = '';
$oe = '';

Next, we make the call to Google. The call() method of the SOAP_Client object expects three parameters:

  • The name of the function to call

  • An array with parameters for the call

  • The namespace for the call

  /* Make the call */
$params = array(
    'key'          => $key,
    'q'            => $query,
    'start'        => $start,
      'maxResults' => $maxResults,
      'filter'     => $filter,
      'restrict'   => $restrict,
      'safeSearch' => $safeSearch,
      'lr'         => $lr,
      'ie'         => $ie,
      'oe'         => $oe
);
$response = $client->call(
    'doGoogleSearch',
    $params,
    array('namespace' => 'urn:GoogleSearch')
);

In this example, we assume that the search call returned something useful, although it might not always do so. The Google API returns the text with XML entities escaped and with some inserted <br> tags. We convert the entities to normal characters using html_entity_decode() and strip all tags with strip_tags():

    /* Display results */
    foreach ($response->resultElements as $result) {
          echo html_entity_decode(
                strip_tags("{$result->title}\n({$result->URL})\n\n")
        ) ;
          echo wordwrap(html_entity_decode(strip_tags($result ->snippet)));
          echo "\n\n----------------------------\n\n";
    }
?>

Now, let's go to the next example where we implement a simple SOAP client and server using the same functions as in the XML-RPC examples.

SOAP Server

Here is the server. First, we include the SOAP_Server PEAR Class. Next, we define a class (Example) with the two functions that we want to export through SOAP. In the hello() method, we use implicit conversion from PHP types to SOAP types; in the add() method, we explicitly define the SOAP type (float):

<?php
     require_once 'SOAP/Server.php';

     class Example {
           function hello ($arg)
           {
                return "Hi {$arg}!";
           }

          function add ($a, $b) {
                return new SOAP_Value('ret', 'float', $a + $b);
          }
    }

To fire up the server and process the request data that is stored in HTTP_RAW_POST_DATA, we instantiate the SOAP_Server class, instantiate the class with our methods, associate the class with the SOAP_Server, and process the request by calling the service() method of the SOAP_Server object. The service method processes the data that was posted to the PHP script, extracts the function name and parameters out of the XML, and calls the function in our Example class:

    $server = new SOAP_Server;
    $soapclass = new Example();
    $server->addObjectMap($soapclass, 'urn:Example');
    $server->service($HTTP_RAW_POST_DATA);
?>

SOAP Client

The client is much like the Google client, except that we used explicit typing for the parameters in the call to the add() method:

#!/usr/local/bin/php
<?php
     /* Include the class */
     require_once 'SOAP/Client.php';

     /* Create the client object */
     $endpoint = 'http://kossu/soap/server.php';
     $client = new SOAP_Client($endpoint);

     /* Make the call */
     $response = $client->call(
           'hello',
           array('arg' => 'Derick'),
           array('namespace' => 'urn:Example')
     );
     var_dump($response);

     /* Make the call */
     $a = new SOAP_Value('a', 'int', 212.3);
     $b = new SOAP_Value('b', 'int', 312.3);
     $response = $client->call(
          'add',
          array($a, $b),
          array('namespace' => 'urn:Example')
     );
     var_dump($response);
?>

This is going over the wire (for the second call). You can see that there is much more XML magic than with XML-RPC:

POST /chap_xml/soap/server.php HTTP/1.0
User-Agent: PEAR-SOAP 0.7.1
Host: kossu
Content-Type: text/xml; charset=UTF-8
Content-Length: 528
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>

<SOAP-ENV:Envelope
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 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/"
 xmlns:ns4="urn:Example"
 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>

<ns4:add>
<a xsi:type="xsd:int">212.3</a>
<b xsi:type="xsd:int">312.3</b></ns4:add>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


HTTP/1.1 200 OK
Date: Tue, 31 Dec 2002 14:56:17 GMT
Server: Apache/1.3.27 (Unix) PHP/4.4.0-dev
X-Powered-By: PHP/4.4.0-dev
Content-Length: 515
Connection: close
Content-Type: text/xml; charset=UTF-8

<?xml version="1.0" encoding="UTF-8"?>

<SOAP-ENV:Envelope
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 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/"
 xmlns:ns4="urn:Example"
 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>

<ns4:addResponse>
<ret xsi:type="xsd:float">524</ret></ns4:addResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

8.7.2.2 PHP's SOAP Extension

PHP 5 also comes with a SOAP extension ext/soap, which has even more features than PEAR::SOAP, and is written in C instead of PEAR::SOAP, which is written in PHP. With this extension, we're going to implement the same examples as in the "PEAR::SOAP" section to show you the differences between the two packages. You need to enable the SOAP extension with the PHP configure option --enable-soap or just uncomment the correct line in your php.ini file in case you're using a Windows version of PHP.

The SOAP extension also supports WSDL (pronounced as "wizdel"), an XML vocabulary used to describe Web Services. With this WSDL file, the extension knows certain aspects such as the endpoint, procedures, and message types with which you can connect to an end point. Google's Web API SDK package (which you can download at http://www.google.com/apis/download.html) includes such a WSDL description file, but we cannot republish this WSDL file here, of course. What we can do is show you an example on how to use it:

#!/usr/local/bin/php
<?php
    /* Read the search string from the command line */
    if ($argc != 2) {
        echo "usage: ./google.php searchstring\n\n";
        exit();
    }
    $query = $argv[1];


    /* Defining the 'license' key */
    $key = 'b/Wq+3hQFHILurTSX6USaub3VeRGsdSg';


    /* Defining maximum number of results and starting index */
    $maxResults = 3; $start = 0;


    /* Setup the other parameters */
    $filter = FALSE; $restrict = ''; $safeSearch = FALSE;
    $lr = ''; $ie = ''; $oe = '';


    /* Make the call */
    $client = new SoapClient('GoogleSearch.wsdl');
    $res = $client->doGoogleSearch(
        $key, $query, $start, $maxResults, $filter, $restrict,
        $safeSearch, $lr, $ie, $oe
    );


    /* Display results */
    foreach ($res->resultElements as $result) {
        echo html_entity_decode(
            strip_tags("{$result->title}\n({$result->URL})\n\n")
        );
        echo wordwrap(html_entity_decode(strip_tags($result ->snippet)));
        echo "\n\n----------------------------\n\n";
    }
?>

As you compare this script with the one we used for PEAR::SOAP, you see that calling a SOAP method with WSDL is much easierit's only two lines!

SOAP Server

Developing a SOAP server and its accompanying WSDL file is not that hard, either; the largest problem is creating the WSDL description file. The WSDL file is not included here, but can be found in the examples archive belonging to this book. Here is the code for the server:

<?php
    class ExampleService {


        function hello ($name) {
            if (strlen($name)) {
                return "Hi {$name}!";
            } else {
                throw new SoapFault("Server", "No name :(.");
            }
        }
    }

It's basically just a normal PHP class, the only difference being the SoapFault exception which is the SOAP way of returning errors. We'll see in the client code how to handle this:

    $server = new SoapServer("example.wsdl");
    $server->setClass("ExampleService");
    $server->handle();
?>

This connects the class that is providing the method with help of the WDSL file to the SOAP server. The handle() method takes care of processing the information when a client requests a method call.

SOAP Client

The client looks like this:

<?php
    $s = new SoapClient('example.wsdl');


    try {
        echo $s->hello('Derick'), "\n";

This first call is correct, as we supply a parameter to the function:

echo $s->hello(), "\n";

This one will throw the SOAP fault exception because the name parameter will be empty:

    } catch (SoapFault $e) {
        echo $e->faultcode, ' ', $e->faultstring, "\n";
    }
?>

If we don't catch this exception, the script will die with a fatal error. Now, it will show this when executed:

Hi Derick!
SOAP-ENV:Server No name :(.

    Team LiB
    Previous Section Next Section