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:
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:
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.
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:
$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
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:
$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');
/* 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 if(isset($winner)): // only display the results if a search was carried out ?>
<div>
<h2>The Winner is <?php echo(htmlspecialchars($winner->term)) ?></h2>
<?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.