-----------------------------------------------
PHP Web Services using SOAP
-----------------------------------------------
Written by Giovanni Tropeano 11/2004
Prepared for OSIX
<<< Table of Contents >>>
- ...Preface
- ...Web Services Using NuSOAP
- ...Installing, Configuring NuSOAP
- ...Data Type Issues
- ...Our First SOAP Client
- ...Our First SOAP Server
- ...Dealing with FAULTS
- ...Using Arrays
- ...WSDL and soap_proxy
- ...Summary
Ok, today we'll take a look at using web services in PHP. All I can say about this one is...buckle up. This'll be a ride. I wrote this for one reason - I kept putting off learning this topic. First of all, PHP is not one of my strong points. Secondly, PHP is NOT one of my strong points :) But...this is OSIX. PHP and NuSOAP both Open Source. I couldn't resist. Thanks to my former employer for making me learn web services in PHP via hours and hours of online courses, heeeeere we go...
So what is NuSOAP you ask? Well, it's a collection of classes that allow you to send and receive SOAP messages over HTTP. It's distributed by the fine people at http://www.nusphere.com and yes, it's all open source.
NuSOAP isn't a PHP extension. It's written in pure PHP. This means all PHP developers, no matter what their web server permissions or restrictions are, can use it. Nice.
NuSOAP is a component-based Web Services toolkit. It employs a base class that provides utility methods such as variable and envelope serialization, as well as namespace information and mappings of different types to different namespaces. Web Service interaction is achieved through a high-level client class called soapclient. This high-level class allows users to specify options such as HTTP authorization credentialls, HTTP proxy information, as well as managing the actual sending and receiving of the SOAP message itself. It uses several helper classes to accomplish the sending and receiving of SOAP messages.
You can execute SOAP operations by passing the name of the operation to the call() method. If the service to be consumed provides a WSDL file (always nice when they do, huh?), the soapclient class takes the URL of the WSDL file as an argument to its constructor, and uses the wsdl class to parse the WSDL file and extract all the data. The WSDL class has methods that extract data on a per-operation or per-binding basis.
The soapclient uses this data from the WSDL file to encode parameters and create the SOAP envelope when the user executes a call to a service. When the call is executed, the soapclient class uses the soap_transport_http class to send the outgoing message and receive the incoming message. The incoming message is parsed using soap_parser class.
If the Web Service to be consumed doesn't provide a WSDL file (the bastards!), then the process is different. The URL of the service is passed to the soapclient class constructor. Operations are still executed using the call method of the soapclient object, but details that are otherwise provided by the WSDL file must be passed as arguments. Parameters that are custom types can be represented using the soapval classs, which allow users to customize a parameter's serialization. With me? Good, let's "install" NuSOAP.
Installing NuSOAP is easy. It's really just an include page. I'm sure you probably know this , but for the newbies... Follow these steps:
1. Download the files from url]http://dietrich.ganx4.com/nusoap/[/url].
2. Extract the file nusoap.php from its zip (if you chose the zip version).
3. For easy access, copy the classes to a location in your include path, or into the directory in which you'll be using the classes.
4. Include the class in your script. The path to nusoap.php can be relative or absolute:
include('nusoap.php');
In my example, I have used the include() function to include the NuSOAP classes in my script. This function will generate a warning if the path to nusoap.php is incorrect, but will continue to process the rest of the script. There are other ways to do this though:
require()
This function is identical to include(), but handles failure by spitting out a fatal error, which will halt processing of the script.
or...
require_once()
Identical to require(), except that if the file to be included is already included in the script, it will not repeat the inclusion.
or...
include_once()
Identical to include(), except that if the file to be included has been previously included in the script, it will not include it again.
SOAP and WSDL both make heavy use of the data types described in the XML Schema specification. This can be a problem since PHP does not natively support most of the data types defined in the specification. Also, the XML Schema data types are 'fine-grained' and 'explicitly defined', and PHP is a loosely-typed language and will convert data types automatically when deamed appropriate. NuSOAP solves this problem at three different levels (maybe more, but three that I know of):
1. In WSDL, NuSOAP's soapclient class will encode a value's type according to the type specified in the WSDL document.
2. The NuSOAP soapval class allows users to explicitly define a value's type.
3. If no type is explicitly declared when instantiating a soapval object, NuSOAP will analyze the value passed to it using PHP's built-in variable introspection functions as well as regular expressions and other means where necessary, and classify it as a valid XML Schema data type.
We're going to start off really simple. Our Web Service will accept a string as a parameter, and echo it to the screen. Probably won't impress the hottie down the block, but...we have to start somewhere.
I'm going to demonstrate the basic process of creating a SOAP client, calling a SOAP service and passing it parameters, and receiving the response. We will name this file echoStringClient.php.
We'll use the require() function in our examples because we'd like the script to halt execution if it cannot find the NuSOAP file:
<?php
require('nusoap.php');
Let's create a variable for the string we'd like to send.
$myString = 'I learned this from OSIX';
Our parameters must be passed as an array to the SOAP client, so let's create one:
$parameters = array($myString);
Now we can instantiate the soapclient object. It takes the URL of the server as an argument to its constructor:
$s = new soapclient('http://MySite.com/echoStringServer.php');
This step is where all the magic takes place. Using the call() method, we tell the soapclient object which service we'd like to access, then pass our array of parameters, and the method then returns the response from the server. This response is a PHP native type, such as a string, integer, or array. It is the result of the decoding that takes place when NuSOAP parses the response message:
$result = $s->call('echoString',$parameters);
NuSOAP offers error detection through the getError() method. If an error has occurred this method returns a string describing the error, and returns false otherwise. In our example, we print our result after checking for errors if there are none. If there are errors, we'll print the error message:
if(!$err = $s->getError()){
echo 'Result: '.$result;
} else {
echo 'Error: '.$err;
}
You might find this last bit of code helpful for debugging SOAP operations. The request and response properties of the soapclient class contain strings of the respective messages, including the HTTP headers sent with each:
echo '<xmp>'.$s->request.'</xmp>';
echo '<xmp>'.$s->response.'</xmp>';
?>
SOAP Request and SOAP Response
Here is the request message from our example:
POST /echoStringServer.php HTTP/1.0
User-Agent: NuSOAP v0.6.1
Host: localhost
Content-Type: text/xml
Content-Length: 569
SOAPAction: ""
<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
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:si="http://soapinterop.org/xsd">
<SOAP-ENV:Body>
<nu:echoString xmlns:nu="http://testuri.org">
<soapVal xsi:type="xsd:string">I learned this from OSIX</soapVal>
</nu:echoString>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Below is the server's response message from our kick ass high tech example. Notice that the first element in the body of the message is the name of the operation we called, with Response appended to it. Also, the element name of the return value is called return. Both of these are standard practice for serializing SOAP responses:
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Fri, 04 Nov 2004 18:47:53 GMT
X-Powered-By: PHP/4.1.2
Status: 200 OK
Server: NuSOAP Server v0.6.1
Connection: Close
Content-Type: text/xml; charset=UTF-8
Content-Length: 1525
<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
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:si="http://soapinterop.org/xsd">
<SOAP-ENV:Body>
<echoStringResponse>
<soapVal xsi:type="xsd:string">I learned this from OSIX</soapVal>
</echoStringResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This example is the server that was accessed by our client example. It implements the echoString service.
The first step as usual is to include the NuSOAP classes:
<?php
require('nusoap.php');
Now we can instantiate the server object, provided by the soap_server class:
$s = new soap_server;
To allow our function to be called remotely, we must register it with the server object. If this is not done, the server will generate a fault indicating that the service is not available if a client accesses the service. In the absence of such a registration process, any PHP functions would be remotely available, which would present a serious security risk:
$s->register('echoString');
Now we can define our function that we are exposing as a service. Notice that we first check to make sure a string was passed. If the parameter is not a string, we use the soap_fault class to return an error to the client indicating that they must pass a string value as the parameter to this function:
function echoString($inputString){
if(is_string($inputString)){
return $inputString;
} else {
return new soap_fault('Client','','Hey! Pass me a string would ya!.');
}
}
The final step is to pass any incoming posted data to the SOAP server's service method. This method processes the incoming request, and calls the appropriate function. It will then formulate the ressponse, and print it:
$s->service($HTTP_RAW_POST_DATA);
?>
The properties of the soap_fault class are below. These are also the arguments to the soap_fault constructor, in the same order as below:
Fault Name: faultcode
This property MUST have a value. The values available to the user are Client and Server. Client class errors indicate that the message didn't contain the information required for the operation to succeed. Server class errors indicate processing problems on the server.
Fault Name: faultactor
This property is not functional yet in NuSOAP, and can be left empty. Not sure why they even put it in their documentation. And not sure why I put it in here!
Fault Name: faultstring
This is a human-readable error message. This is the best place for you to describe errors.
Fault Name: faultdetail
The value of the faultdetail property is XML data used to detail the specific errors related strictly to the Body element of the SOAP message. You can insert your own XML markup here if you'd like to.
The soap_fault class has only one method besides the constructor that is serialize(). The serialize() method takes the fault information and serializes it, returning a complete SOAP message.
An example of using the soap_fault class is shown here:
$fault = new soap_fault(
'Client','','Whassamatta wit you huh, the inputString parameter cannot be empty!');
echo $fault->serialize();
The SOAP message below is what is returned by the serialize() method of the soap_fault object instantiated above:
<?xml version="1.0"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
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:si="http://soapinterop.org/xsd">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>Client</faultcode>
<faultactor></faultactor>
<faultstring>Whassamatta wit you huh, the inputString parameter cannot be empty!</faultstring>
<detail></detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Transmitting and receiving arrays is transparently done in NuSOAP. You can pass PHP arrays in the parameters array argument to the soapclient class's call method, and NuSOAP will detect the type of the array contents, and serialize accordingly. This next example uses a service called echoArray that accepts an array and returns the same array.
Homework: While running this example, try modifying the parameter array by adding different data types, and see how the serialization changes. Sweet huh?
First, include the NuSOAP classes:
<?php
require('nusoap.php');
Create the array we'd like to send:
$arr = array('string1','string2');
Create the array of parameters:
$parameters = array($arr);
Instantiate the soapclient object, passing it the endpoint URL:
$s = new soapclient('http://MySite.com/echoStringServer.php/echoArrayServer.php');
Now we call the echoArray operation, passing it our array of parameters, and receive the result. Then print the result on the screen, and view the request and response messages, as follows:
$result = $s->call('echoArray',$parameters);
if(!$err = $s->getError()){
echo 'Result: '.$result;
} else {
echo 'Error: '.$err;
}
echo '<xmp>'.$s->request.'</xmp>';
echo '<xmp>'.$s->response.'</xmp>';
?>
WSDL is an XML language used to describe a Web Service. It is a machine-readable format that provides Web Service clients with all the information necessary to access the service. NuSOAP provides a class for parsing WSDL files, and extracting data from them. The soapclient object uses the wsdl class to ease the burden of the developer callling a service. With the help of the WSDL data to create messages, you only need to know the name of the operation to call, and the parameters required by the operation.
Using WSDL with NuSOAP provides several benefits in my opinion:
Benefit #1: All service meta data such as namespaces, endpoint URLs, parameter names, and much more are read from the WSDL, allowing the client to dynamically cope with changes from the server. This information no longer needs to be hard-coded into the user's script since it's on the server.
Benefit #2:It allows us to use the soap_proxy class. This class is an extended soapclient class with new methods for each of the operations detailed in the WSDL file. Now you can call these methods directly. I will describe this process below.
The soapclient class contains a method called getProxy(). This method returns an object of the class soap_proxy. The soap_proxy class extends the soapclient class with methods that correspond to the operations defined in the WSDL document, and allows users to call the remote methods of an endpoint as if they were local to the object. This is only functional when the soapclient object has been instantiated using a WSDL file and has the advantage of easy access for a user.
Mobster WSDL example
Okay, it's important for mobsters to know the life status of their enemies. Imagine if you planned to whack someone and they were already dead? You have just wasted many hours of drinking at the titty bar and planning this hit. So, you'd better call on your handy dandy Mobster web service to quickly help you.
Here is an example of using WSDL to call your service, using the soap_proxy class. We start by including the NuSOAP classes:
<?php
include('nusoap.php');
When instantiating the client object using WSDL, we pass the URL or path to the WSDL file as an argument to the constructor, as well as an argument that lets the client know we've passed it WSDL and not a SOAP endpoint:
$s = new soapclient('http://MyMobSite.com/soap/urn:whack_status.wsdl', 'wsdl');
Now we'll generate the proxy class. This is achieved by invoking the getProxy() method of the soapclient class:
$p = $s->getProxy();
We can now invoke the remote method as a method of the proxy class, and pass our parameters directly. The proxy object will handle the details such as matching up parameter values to their names, assigning namespaces, and types:
$lifeStatus = $p->getMobsterStatus('Bugsy');
Lastly let's check for errors, and if none are present print out the results:
if(!$err = $p->getError()){
print "That mobster life status is: $lifeStatus .";
} else {
print "ERROR: $err";
}
print '<xmp>'.$p->request.'</xmp>';
print '<xmp>'.str_replace('><',">\n<",$p->response).'</xmp>';
?>
Our code may produce the following:
That mobster life status is: DEAD. No need to Whack!
(quick note to all potential mobsters: this is a ficticious web service. please do not use this to plan your whacks.)
Well, thanks for reading this. I had a blast writing it, and brushing up on my PHP. As I mentioned, I am NOT an expert at PHP. There may be other ways to achieve easy web services in PHP, but this way just seemed to be easiest for me.
And the migration for me continues...slowly converting to PHP from .NET and loving the ride.
TroPe
|