Programming with NuSOAP

NuSOAP is a group of PHP classes that allow developers to create and consume SOAP web services. It does not require any special PHP extensions. The current stable version (0.6.3) of NuSOAP at the time this was written (23-April-2003), supports much of the SOAP 1.1 specification. It can generate WSDL 1.1 and also consume it for use in serialization. Both rpc/encoded and document/literal services are supported. However, it must be noted that NuSOAP does not provide coverage of the SOAP 1.1 and WSDL 1.1 that is as complete as some other implementations, such as .NET and Apache Axis.

This document follows up Introduction to NuSOAP with additional samples that demonstrate how to use NuSOAP to create and consume SOAP web services.

Hello, World Redux
Simple Types
SOAP Faults
Resources

Hello, World Redux

Showing no imagination whatsoever, I used the ubiquitous "Hello, World" example in Introduction to NuSOAP. In that document, I showed the SOAP request and response exchanged by the client and server. The first thing we will explore here is how this sample can be improved, as well as the limits of this improvement.

There were two aspects of the XML payload in the SOAP response that could be improved. First, the response element should have a namespace. Second, the first child of the response should be given a meaningful name, rather than just accepting the default name soapVal.

One would think that the namespace of the response element could be set with the $namespace parameter to the soap_server->register method. Likewise, it would seem possible to set the name of the return element by specifying it in the output parameter array. However, the code with these changes, shown below, returns the same SOAP response as the original helloworld.php sample. Interestingly, although a namespace and SOAPAction are specified, the client need not provide these in its SOAP request.

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the server instance
$server = new soap_server;
// Register the method to expose
// Note: with NuSOAP 0.6.3, only method name is used w/o WSDL
$server->register(
    'hello',                            // method name
    array('name' => 'xsd:string'),      // input parameters
    array('return' => 'xsd:string'),    // output parameters
    'uri:helloworld',                   // namespace
    'uri:helloworld/hello',             // SOAPAction
    'rpc',                              // style
    'encoded'                           // use
);
// Define the method as a PHP function
function hello($name) {
    return 'Hello, ' . $name;
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>

The client code that follows specifies the namespace and SOAPAction for the call. Although, as noted above, the NuSOAP service appears to ignore these, it is a good practice to include them, since they will be required to interoperate with most services based on other SOAP implementations, such as .NET or Apache Axis.

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the client instance
$client = new soapclient('http://localhost/phphack/helloworld3.php');
// Check for an error
$err = $client->getError();
if ($err) {
    // Display the error
    echo '<p><b>Constructor error: ' . $err . '</b></p>';
    // At this point, you know the call that follows will fail
}
// Call the SOAP method
$result = $client->call(
    'hello',                     // method name
    array('name' => 'Scott'),    // input parameters
    'uri:helloworld',            // namespace
    'uri:helloworld/hello'       // SOAPAction
);
// Strange: the following works just as well!
//$result = $client->call('hello', array('name' => 'Scott'));
// Check for a fault
if ($client->fault) {
    echo '<p><b>Fault: ';
    print_r($result);
    echo '</b></p>';
} else {
    // Check for errors
    $err = $client->getError();
    if ($err) {
        // Display the error
        echo '<p><b>Error: ' . $err . '</b></p>';
    } else {
        // Display the result
        print_r($result);
    }
}
?>

So, the server uses only the method name we specified when registering it. We can still control the name (and type) of the response data. Instead of returning just a string as we have been, we can return a soapval, a class provided by NuSOAP. Here is a variation of the original helloworld.asp that does just that.

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the server instance
$server = new soap_server;
// Register the method to expose
// Note: with NuSOAP 0.6.3, only method name is used w/o WSDL
$server->register(
    'hello',                             // method name
    array('name' => 'xsd:string'),       // input parameters
    array('return' => 'xsd:string'),     // output parameters
    'uri:helloworld',                    // namespace
    'uri:helloworld/hello',              // SOAPAction
    'rpc',                               // style
    'encoded'                            // use
);
// Define the method as a PHP function
function hello($name) {
    return new soapval('return', 'xsd:string', 'Hello, ' . $name);
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>

If you look at the constructor for the soapval class, you will see that we could also specify a namespace for the element. We really want to specify the namespace of the element containing the return value, so there is no need to specify a namespace here.

The response from this new service is shown below. Note that the element contained by the response element now has the name return instead of soapVal.

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Thu, 24 Apr 2003 15:15:28 GMT
X-Powered-By: PHP/4.0.6
Server: NuSOAP Server v0.6.3
Connection: Close
Content-Type: text/xml; charset=UTF-8
Content-Length: 522

<?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>
  <helloResponse>
   <return xsi:type="xsd:string">Hello, Scott</return>
  </helloResponse>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Return to top.

Simple Types

The Hello, World example uses a string for a parameter and a string for the return value. We can use other scalar, or simple, types as well. PHP4 supports string, integer, float and boolean types. The following service has a method that uses all four.

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the server instance
$server = new soap_server;
// Register the method to expose
$server->register('joinparams');
// Define the method as a PHP function
function joinparams($s, $i, $f, $b) {
    $ret = $s . ' is a ' . gettype($s);
    $ret .= ' ' . $i . ' is a ' . gettype($i);
    $ret .= ' ' . $f . ' is a ' . gettype($f);
    $ret .= ' ' . $b . ' is a ' . gettype($b);

    return new soapval('return', 'xsd:string', $ret);
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>

Here is a client for this.

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the client instance
$client = new soapclient('http://localhost/phphack/scalar.php');
// Call the SOAP method
$result = $client->call(
                'joinparams',
                array('s' => 'foo', 'i' => 21, 'f' => 43.21, 'b' => true)
            );
// Check for a fault
if ($client->fault) {
    echo '<p><b>Fault: ';
    print_r($result);
    echo '</b></p>';
} else {
    // Check for errors
    $err = $client->getError();
    if ($err) {
        // Display the error
        echo '<p><b>Error: ' . $err . '</b></p>';
    } else {
        // Display the result
        print_r($result);
    }
}
?>

The result is shown below.

foo is a string 21 is a integer 43.21 is a double 1 is a integer

Does the result surprise you? The fourth parameter, for which we supplied a boolean, appears to be an integer in the service. The first thing to check is the SOAP request, which looks like the following.

POST /phphack/scalar.php HTTP/1.0
User-Agent: NuSOAP/0.6.3
Host: localhost
Content-Type: text/xml; charset="ISO-8859-1"
Content-Length: 630
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>
  <ns1:joinparams xmlns:ns1="http://testuri.org">
   <s xsi:type="xsd:string">foo</s>
   <i xsi:type="xsd:int">21</i>
   <f xsi:type="xsd:float">43.21</f>
   <b xsi:type="xsd:boolean">1</b>
  </ns1:joinparams>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This shows that the fourth parameter was, in fact, specified to be a boolean in the XML schema. This illustrates one of the "quirks" of NuSOAP. When scalar values are deserialized from the XML payload, the XML type information is ignored. Besides booleans, this can affect strings in a bad way. For example, a string that is all digits will be deserialized into an integer. This is bad in the case where the string is something like a U.S. postal code, in which leading zeroes are significant. It is also bad in the case where there are too many digits to successfully convert the value to an integer. PHP will generate an overflow error.

This example also leads to a discussion of PHP, which uses run-type (also called weak) typing of variables. The data types of the parameters to a NuSOAP service method cannot be enforced. (Actually, they can be enforced using WSDL, but we are not up to that point yet.) Although it is my intention that the method be called with a string, integer, float and boolean, in that order, PHP and NuSOAP do not enforce that. If the data types are important to you, your code must check them. The next section will show an example of that.

Return to top.

SOAP Faults

The SOAP 1.1 specification defines a method for reporting errors on the server back to the client. This is the SOAP Fault. The client code we have been using checks for faults and displays any that are received. The NuSOAP server code will automatically generate faults in some cases, such as when the client requests a method that does not exist. The service code you write can also generate faults.

In the previous example, suppose we wanted to enforce data types on the incoming values. We could check the data types and return a SOAP Fault if any are incorrect. The modified service code is shown below. Note that, based on what was discovered about NuSOAP's de-serialization scalar values, the service method must check the variable types more loosely than you might expect. For example, since the string value '55.55' will be de-serialized as a float, we allow float values to be specified for the string (and our code would then cast the float to a string).

<?php
// Pull in the NuSOAP code
require_once('nusoap.php');
// Create the server instance
$server = new soap_server;
// Register the method to expose
$server->register('joinparams');
// Define the method as a PHP function
function joinparams($s, $i, $f, $b) {
    if (!(is_string($s) || is_int($s) || is_float($s))) {
        return new soap_fault('SERVER', '', 's must be a string', $s);
    }
    if (!is_int($i)) {
        return new soap_fault('SERVER', '', 'i must be an integer', $i);
    }
    if (!(is_float($f) || is_int($f))) {
        return new soap_fault('SERVER', '', 'f must be a float', $f);
    }
    if (!(is_bool($b) || is_int($b))) {
        return new soap_fault('SERVER', '', 'b must be a boolean', $b);
    }

    $ret = $s . ' is a ' . gettype($s);
    $ret .= ' ' . $i . ' is a ' . gettype($i);
    $ret .= ' ' . $f . ' is a ' . gettype($f);
    $ret .= ' ' . $b . ' is a ' . gettype($b);

    return new soapval('return', 'xsd:string', $ret);
}
// Use the request to (try to) invoke the service
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>

By providing a client that specifies an incompatible type for a parameter, such as the string 'bar' for the integer parameter $i, you can see that the server now returns a SOAP Fault to the client. Here is what the response looks like in that case.

HTTP/1.1 500 Internal Server Error
Server: Microsoft-IIS/5.0
Date: Thu, 24 Apr 2003 16:08:50 GMT
X-Powered-By: PHP/4.0.6
Server: NuSOAP Server v0.6.3
Connection: Close
Content-Type: text/xml; charset=UTF-8
Content-Length: 620

<?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>SERVER</faultcode>
   <faultactor></faultactor>
   <faultstring>i must be an integer</faultstring>
   <detail>
    <soapVal xsi:type="xsd:string">bar</soapVal>
   </detail>
  </SOAP-ENV:Fault>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Return to top.

You can download the source for these examples as well.

Return to top.

Resources

Join the NuSOAP mailing list to learn more and ask questions.
The home of the NuSOAP project.
NuSOAP home of Dietrich Ayala, the author of NuSOAP.

Return to top.

Copyright © 2003 Scott Nichol.
24-Apr-2003