Sr. Web Developer
mediabistro.com
US-NY-New York

Justtechjobs.com Post A Job | Post A Resume

Using XML, Part 5: SOAP and WSDL
In the previous article in this series I demonstrated how XML is used to make remote procedure calls with XML-RPC in PHP. This article will focus on SOAP and WSDL (both of which use XML as their underlaying method of describing data) and demonstrate how a PHP script can act as a SOAP client and auto-magically discover detailed information about a web service.

An overview of Soap
SOAP, originally an acronym for Simple Object Access Protocol, is a lightweight XML-based messaging protocol designed to work in a distributed environment. It provides a foundation layer which can be built upon to facilitate complex messaging among web services. SOAP messages are encoded using XML and XML-RPC messages, and they are often sent over an HTTP connection. A simple SOAP envelope (message) that asks for the time may look like this:

<?xml version="1.0" encoding="UTF-8" ?>
<soap:Envelope xmlns:soap="http://shcemas.xmlsoap.org/soap/envelope/" >
    <soap:Body>
        <getTime xmlns="http://www.phpbuilder.com/adam_delves/fourth_dimension/">
            <timeZone>London/Europe</timeZone>
        </getTime>
    </soap:Body>
</soap:Envelope>


SOAP makes use of XML name spaces. The URI by convention should link to a schema document defining the name space. The soap name space contains the soap envelope and the default name space used in the getTime element contains the message. The response might look as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<soap:Envelope xmlns:soap="http://shcemas.xmlsoap.org/soap/envelope/" >
    <soap:Body>
        <getTimeResponse xmlns="http://www.phpbuilder.com/columns/adam_delves/fourth_dimension/">
            <time>
                <hour>12</hour>
                <minute>34</minute>
                <second>10</second>
            </time>
            <format>24h</format>
        </getTimeResponse>
    </soap:Body>
</soap:Envelope>


Remote Procedure Call Using Soap
One of the applications of SOAP is to describe the message patterns of RPC calls and responses between web services. The format of an RPC call contained in a SOAP envelope is slightly different and more complex than an XML-RPC message. However, its complexity allows for scalability in describing more complex service structures and data types.

A SOAP RPC Call
<?xml version="1.0" encoding="UTF-8" ?>
<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:s-enc="http://schemas.xmlsoap.org/soap/encoding/"
    soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding" >

    <soap:Body>
        <verify xmlns="http://www.phpbuilder.com/columns/adam_delves/email_validator.verify/">
            <symbol xsi:type="xsd:integer">54</symbol>
            <symbol xsi:type="xsd:string">RG5H4</symbol>
        </verify>
    </soap:Body>
</soap:Envelope>


A SOAP RPC Response
<?xml version="1.0" encoding="UTF-8" ?>
<soap:Envelope
    xmlns:soap="http://shcemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:s-enc="http://schemas.xmlsoap.org/soap/encoding/"
    soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding" >

    <soap:Body>
        <verifyResponse xmlns="urn:webservices-email_validator">
            <Result xsi:type="xsd:boolean">false</Reponse>
        </verifyResponse>
    </soap:Body>
</soap:Envelope>


In the above example, 4 additional namespaces are used.
  • http://shcemas.xmlsoap.org/soap/envelope/ – is the XML Schema language. This contains the basic data types.
  • http://www.w3.org/2001/XMLSchema – Used in xsi:type attributes to specify the data type.
  • http://schemas.xmlsoap.org/soap/encoding/ – SOAP specific type encodings.
  • http://www.phpbuilder.com/columns/adam_delves/email_validator.verify/ - this URI does not exist but serves to identify the name space for the verify method of the email validator
Soap-RPC and WSDL
On its own, an RPC called using SOAP looks like a bloated XML-RPC call. Using XML, RPC would be a lot less expensive, parsing wise, than using SOAP. Web Services Description Language (WSDL) serves to put SOAP-RPC into context. A WSDL file which compliments a web service describes all the operations and data types that the web service interface exposes. This is especially useful in object-orientated programming languages as it enables the client using the web service to automatically replicate any required objects, methods and data types and decode the SOAP responses appropriately.
WSDL is an XML-based language. The client initialises their web service with the URI or path of the WSDL file (which need not reside on the same server as the service endpoint) and generates proxy code on the fly.

Using Soap in PHP 5
As SOAP is not enabled by default in PHP, you may need to enable it before continuing. There are two main SOAP implementations available, PEAR-SOAP and PHP's own SOAP extension. Where it is available PHP's own extension should be used, as it is written in C, and is faster and more efficient than the PEAR implementation. In this article, the code samples will apply to PHP's SOAP extension.

Installation
PHP's SOAP extension is not enabled by default in PHP. It is however included as part of the source code and as a dll file in the Windows ZIP distribution. To check whether the SOAP extension is already enabled, create a file containing a call to phpinfo() and check for a section named SOAP.

Windows
To enable the SOAP extension on a Windows system you must add the following line to your php.ini configuration file in the extensions section and ensure that the file php_soap.dll is contained in the directory specified in the extension_dir setting:
extension=php_soap.dll

If the php_soap.dll file is not present on your system, download the latest Windows version of PHP distributed as a ZIP file from the PHP web site.

Unix
To enable SOAP support on a UNIX system, you must re-compile the PHP interpreter. The SOAP extension also requires a libxml-2.5.4 or greater, which can be obtained from XMLSoft, assuming none are present on your system. First download and install libxml, then recompile PHP.

# wget ftp://xmlsoft.org/libxml2/libxml2-2.6.11.tar.gz
# tar xzvf libxml2-2.6.11.tar.gz
# cd libxml2-2.6.11
# ./configure
# make
# su -c "make install"

# wget http://uk2.php.net/get/php-5.1.4.tar.gz/from/uk.php.net/mirror
# tar zxvf php-5.1.4.tar.gz
# cd php-5.1.4
# ./configure --enable-soap
# make
# su -c "make install"

Creating a SOAP Client in PHP
The easiest method of creating a Soap Client is to give it the URL or path of the WSDL file containing all the information on how to interact with the web service. At this point, the object is initalised with all the methods of the web service and can be called as functions of the soap client.
The following PHP code makes a request to one of the Amazon Web Services API's:

PHP:

$params->AWSAccessKeyId = AMAZON_API_KEY;
$params->Request->SearchIndex 'Books';
$params->Request->Keywords 'php5 oop';
   
$amazon = new SoapClient('http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl');
$result $amazon->itemSearch($params);


Note: Before using this code you need to obtain your own API Key.

  • The Amazon web service API's require the parameters to be sent in the form of an object. This is initialized with all the appropriate values first.
  • First an instance of the SoapClient object is created using the URI of the WSDL file.
  • The itemSearch() operation which is now a method of the SoapClient object is called. This causes PHP to make an HTTP request to the web service, sending a SOAP envelope containing the RPC call.
  • The return value of the itemSearch operation sent back from the web service is returned by the itemSearch() method and is decoded into native PHP types automatically.
  • The result, like the parameter passed to the function, is in the form of an object. If the request is successful, the Item property will contain an array of Item objects for the items found.

Generating PHP Code using WSDL
When initialised with a WSDL file, the __getTypes() function of the SoapClient object returns useful information about the data types supported by the web service. Being an untyped language, most of this information is irrelevant to us. However, the object data types can still be replicated in PHP and mapped to the data types exposed by the web service.

Using the Proxy Generator Class
The proxy generator class creates and returns an instance of the SoapClient. However, prior to this it uses the __getTypes() function to create all the class objects and maps them in the classmap array passed in the SoapClient's constructor.

PHP wsdl_proxy_generator.php:


class 
WSDLProxyGenerator
{
       
    
/**
     * Creates an instance of a SoapClient object and all necessary suporting types.
     *
     */
    
public static function createSoapClient($wsdlUri$clientName='WsdlGerneratedWebService')
    {
        
/* create the SoapClient object using the wsdl file */
        
$soap = new SoapClient($wsdlUri);   
        
$types = array();
           
        foreach(
$soap->__getTypes() as $type) {
           
            
/* match the type information  using a regualr expession */
            
preg_match("/([a-z0-9_]+)\s+([a-z0-9_]+(\[\])?)(.*)?/si"$type$matches);

            
$type $matches[1];
               
            switch(
$type) {
                
/* if the data type is struct, we create a class with this name */
                
case 'struct':
                    
/* the PHP class name will be ClientName_WebServiceName */
                    
$className $clientName '_' $name;
                       
                    
/* store the data type information in an array for later use in the classmap */
                    
$types[$name] = $className;
                       

                    
/* check the class does not exsits before creating it */
                    
if (! class_exists($className)) {
                        eval(
"class $className {}");
                    }
                       
                    break;
            }
        }

        
/* create another instance of the SoapClient, this time using the classmap */           
        
return new SoapClient($wsdlUri, array('classmap' => $types));
    }
       
    
/* this class cannot be instantiated as an object */
    
private function __construct()
    {           
    }
               
}


You'll notice that the SoapClient object is created twice using the URI of the WSDL file. This does not, however, mean that two HTTP requests are made and that the WSDL file is parsed twice. PHP's soap extension also includes a WSDL caching feature, which lessens the burden of parsing WSDL files on the server. The caching feature is controlled by three directives in the php.ini configuration file, which can also be set using the ini_set() function.
  • soap.wsdl_cache_enabled – turns the caching feature on or off. On by default
  • soap.wsdl_cache_dir  - the directory where cached WSDL data is stored
  • soap.wsdl_cache_ttl – number of seconds before a cached WSDL file is considered stale and re-fetched, defaults to 86400 seconds (1 day)
With the help of the proxy generator, the SoapClient object now automatically decodes objects in the method response into their corresponding PHP types. This enables the functionality of the data types to be extended easily:

PHP:


class 
Amazon_Item
{
    public function 
getDetailedItem()
    {
        global 
$amazon;

        
$params->AWSAccessKeyId AMAZON_API_KEY;
        
$params->Request->ItemId $this->ASIN;
        
$params->Request->ResponseGroup 'Large'// this parameter requests detailed information on item
       
        
return $amazon->itemLookUp($params)->Items->Item;
    }
}

$params->AWSAccessKeyId AMAZON_API_KEY;
$params->Request->SearchIndex 'Books';
$params->Request->Keywords 'php5 oop';
   
$amazon WSDLProxyGenerator::createSoapClient('http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl''Amazon');
$result $amazon->itemSearch($params);

print_r($result->Items->Item[0]->getDetailedItem());


The modified code now uses the proxy generator class to create an instance of the soap client and extends the functionality of the Amazon_Item object to include a function which performs a detailed lookup on the item. Because the result from the itemSearch has been assigned all the appropriate data types, the new method can be called directly on each item returned.

Google API – Google Race
Sites such as Frappr, BBC News Maps and Retrievr all integrate one or more third party web services with their own application in order to enhance functionality. These sites are known as mash-ups and are becoming more common place now that popular sites such as Amazon, Google and eBay are making their API's available to developers. The next example will use PHP to create a simple example of a mash-up using the Google API.

Google Race
Google race will use the Google API to pitch two search terms against each other in a Google search. Each search will be carried out independently and the search that returns the quickest result is the winner. This is almost a complete opposite of Google Battle, where the winner is the search term with the most results.

Like Amazon, you first need to create a Google account and obtain an API Key before using their API functions. This key needs to be passed to each function. You can also download the API documentation and WSDL file. Save the WSDL file in a directory PHP has access to before beginning.

The Code
This is the code responsible for processing the search terms and executing the searches:

PHP - google_race.php:


<?php
require_once 'wsdl_proxy_generator.php';

$temr1 $term2 ''// initialise search term variables

class Google_GoogleSearchResult
{
    public function 
getMs()
    {
        return (int) (
$this->searchTime*1000);
    }
}
   
/* check we have two search terms */
if(isset($_GET['term1'], $_GET['term2'])) {
    
/* don't forget to use stripslashes here, if magic_quotes is turned on */
    
$term1 $_GET['term1'];
    
$term2 $_GET['term2'];

    try {
        
/* initialize the GoogleSearch web service */
        
$googleSearch WSDLProxyGenerator::createSoapClient('GoogleSearch.wsdl','Google');
               
        
/* execute the searches */
        
$result1 $googleSearch->doGoogleSearch(API_KEY$term100,false,'',false,'','','');
        
$result2 $googleSearch->doGoogleSearch(API_KEY$term200,false,'',false,'','','');
           
        
/* add the terms to the two results */
        
$result1->term $term1;
        
$result2->term $term2;
           
        
/* find the winner */
        
if ($result1->searchTime $result2->searchTime) {
            
$winner $result1;
            
$looser $result2;
        } else {
            
$winner $result2;
            
$looser $result1;
        }       

        
/* calculate the length of the bars, inndicating the search times
           the winner is always 300, whereas the looser is the percentage difference */
        
$looser->length 300;
        
$winner->length = (int) (($winner->getMs() / $looser->getMs() )*300);
           
    } catch (
SoapFault $e) {
        
/* an error occured. get the error message and fualtCode */
        
$error $e->getMessage() . '(' $e->faultcode ')';
    }

    include(
'google_race_template.php');
}
?>
Notice the following:
  • The Google_GoogleSearchResult object has been extended to include a function that returns the search time in milliseconds.
  • A try...catch construct is used to catch any errors. This is a very important step, as we are relying on a third party web service. In a production environment, where possible, responses should be cached as a kind of fall back should the web service fail.
  • The length property is added to the Google_GoogleSearchResult objects.
  • No output has been produced here. This part of the script is solely responsible for the processing and execution of the searches.
Now that all the data has been collected, just append an HTML template to the bottom of the script:

PHP - google_race_template.php:

<html>
    <head>
        <title>Google Race!!</title>
        <style type="text/css">
            .bar { position: relative; height: 20px;}
            .sText{    left: 5px; position: relative; top: -20px; }
            a { color: black; font-weight: bold; font-family: sans-serif; }
            #bar1 { background-color: #FF9900; }
            #bar2 {    background-color: #00FFFF; }
        </style>
    </head>
    <body>
        <h1>Google Race</h1>
        <form method="get" action="<?php echo($_SERVER['PHP_SELF']);?>">
            <p>Search term 1 <input type="text" name="term1" value="<?php echo(htmlspecialchars($term1)) ?>" /></p>
            <p>Search term 2 <input type="text" name="term2" value="<?php echo(htmlspecialchars($term2)) ?>"/></p>
            <p><input type="submit" value="Race!!" /></p>
        </form>   
       
        <?php if(isset($winner)): // only display the results if a search was carried out ?>
        <div>
            <h2>The Winner is <?php echo(htmlspecialchars($winner->term)) ?></h2>
           
            <div id="bar1" class="bar" style="width: <?php echo($result1->length?>px;"></div>
            <div class="sText">
                <a href="http://www.google.com/search?q=<?php echo(urlencode($term1)) ?>"><?php echo(htmlspecialchars($term1)) ?></a>
                <?php echo($result1->getMS()) ?>ms
            </div>

            <div id="bar2" class="bar" style="width: <?php echo($result2->length?>px;"></div>
            <div class="sText">
                <a href="http://www.google.com/search?q=<?php echo(urlencode($term2))?>"><?php echo(htmlspecialchars($term2)) ?></a>
                <?php echo($result2->getMS()) ?>ms
            </div>
        </div>
       
        <?php elseif (isset($error)): // display the error message if an error occurred ?>
        <p><b>Error executing search. Please try again later.</b> <?php echo($error?></p>
        <?php endif; ?>   
    </body>
</html>


The template uses PHP's alternate construct syntax to determine whether or not to display the result or an error message.

Performance and Security Considerations
Before taking the plunge and throwing SOAP calls into your PHP application, it is worth taking the following into consideration.
  • If you are creating a web service which only you plan to interact with, consider using XML-RPC. This is less resource intensive on the server and the payloads are smaller. SOAP should be considered where your user base is likely to be large and the API complex.
  • SOAP calls take time as they require your script to make an HTTP request, wait for its response and parse it. Keep the number of calls down to a minimum and in production environments where keeping your users waiting could mean you loose them, implement caching polices that store the responses to common SOAP calls. Some sites also offer paid subscription services that give you priority access and speed when calling their API's.
  • If you are creating a mash-up type site, ensure that you are especially careful where data that pertains to individuals is transferred to a third party web service. This “private” data can be misused by dubious sites. Read the privacy policies of sites offering web service API's carefully and if you are in any doubt at all do not use them.
  • Data received form a web serviced should be treated with the same suspicion as data received from users and needs to be validated and verified before being used.
Conclusion
In this article you have seen how SOAP and WSDL, in conjunction with PHP's SOAP extension, enable your web applications to interact effortlessly with third part web services. You've also seen how WSDL can be used to generate PHP class templates on the fly which can be extended to enrich the functionality of the data returned by the web service.

Next time we will be looking at two XML-based validation languages (Schema and Schematron) and we'll demonstrate how they can be used to ensure that the structure and context of an XML document is valid and its data is in the correct context.

Useful Links