Generating WSDL with NuSOAP - return struct with various types (int, string, array of structs)

Sk8erPeter picture Sk8erPeter · Aug 8, 2011 · Viewed 19.8k times · Source

I would like to query some stuffs via SOAP by generating WSDL with NuSOAP.
I know there are lots of questions related to the topic, but I didn't have success to adapt the codes to my particular problem.

I was successful in generating WSDL code which returns just an array of structs (associative array), BUT I would rather like to return an object (struct) which contains an integer variable, a string variable AND an array of structs.

So, this is the code that works for returning an array of structs:

<?php

    function getStuffs( $user='', $pass='' ) {
        // here we can check user and pass and do whatever (if it isn't alright, we can throw exception or return NULL or sg. similar)
        // .......

        $stuff_array   = array();
        $stuff_array[] = array( 'id'=>122, 'name'=>'One stuff');
        $stuff_array[] = array( 'id'=>213, 'name'=>'Another stuff');
        $stuff_array[] = array( 'id'=>435, 'name'=>'Whatever stuff');
        $stuff_array[] = array( 'id'=>65, 'name'=>'Cool Stuff');
        $stuff_array[] = array( 'id'=>92, 'name'=>'Wow, what a stuff');    

        return $stuff_array;
    }

    require_once 'nusoap/lib/nusoap.php';
    $server = new soap_server;

    // $myNamespace = $_SERVER['SCRIPT_URI'];
    $myNamespace = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];

    $server->configureWSDL('MyStuffService', 'urn:' . $myNamespace);
    // $server->wsdl->schemaTargetNamespace = 'http://soapinterop.org/xsd/';

    $server->wsdl->addComplexType(
        // name
        'Stuffs',
        // typeClass (complexType|simpleType|attribute)
        'complexType',
        // phpType: currently supported are array and struct (php assoc array)
        'struct',
        // compositor (all|sequence|choice)
        'all',
        // restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
        '',
        // elements = array ( name = array(name=>'',type=>'') )
        array(
            'id' => array(
                'name' => 'id',
                'type' => 'xsd:int'
            ),
            'name' => array(
                'name' => 'name',
                'type' => 'xsd:string'
            )
        )
    );  

    $server->wsdl->addComplexType(
        // name
        'StuffsArray',
        // typeClass (complexType|simpleType|attribute)
        'complexType',
        // phpType: currently supported are array and struct (php assoc array)
        'array',
        // compositor (all|sequence|choice)
        '',
        // restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
        'SOAP-ENC:Array',
        // elements = array ( name = array(name=>'',type=>'') )
        array(),
        // attrs
        array(
            array(
                'ref' => 'SOAP-ENC:arrayType',
                'wsdl:arrayType' => 'tns:Stuffs[]'
            )
        ),
        // arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
        'tns:Stuffs'
    );

    $server->register(
        // string $name the name of the PHP function, class.method or class..method
        'getStuffs',
        // array $in assoc array of input values: key = param name, value = param type
        array(
            'user' => 'xsd:string',
            'pass' => 'xsd:string'
        ),
        // array $out assoc array of output values: key = param name, value = param type
        array(
            'return' => 'tns:StuffsArray'
        ),
        // mixed $namespace the element namespace for the method or false
        'urn:' . $myNamespace,
        // mixed $soapaction the soapaction for the method or false
        'urn:' . $myNamespace . "#getStuffs",
        // mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
        'rpc',
        // mixed $use optional (encoded|literal) or false
        'encoded',
        // string $documentation optional Description to include in WSDL
        'Fetch array of Stuffs ("id", "name").' // documentation
    );

    #$server->wsdl->schemaTargetNamespace = $myNamespace;
    $server->service(isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '');
    exit();

?>

In a C# Console Application, after adding a Web Reference called "StuffService" with the "?wsdl" appended to the appropriate URL where this PHP-file can be found, this code works, I can perfectly query the stuff_array values like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WebServiceTest
{
    class Program
    {
        static void Main(string[] args)
        {
            StuffService.MyStuffService myService = new StuffService.MyStuffService();

            StuffService.Stuffs[] stuffs = myService.getStuffs("someone", "1234");

            foreach (var stuff in stuffs)
            {
                Console.WriteLine(stuff.id+".: "+stuff.name);
            }

            Console.WriteLine();
            Console.WriteLine("Press a key...");
            Console.ReadKey();
        }
    }
}

That's cool, BUT I'd like to develop this code to give back an object like this:

class ResponseObject {
    public $responseCode = 0;
    public $responseMessage = '';
    public $stuffArray = NULL;
}   

$responseObject = NULL;

function getStuffs( $user='', $pass='' ) {
    global $responseObject;

    $responseObject = new ResponseObject();

    // check stuffs in a simple way now
    if($user != 'someone' && $pass != '1234'){
        $responseObject->responseCode = 2;
        $responseObject->responseMessage = 'Authentication failed';
        return $responseObject;
    }

    $responseObject->stuffArray   = array();
    $responseObject->stuffArray[] = array( 'id'=>122, 'name'=>'One stuff');
    $responseObject->stuffArray[] = array( 'id'=>213, 'name'=>'Another stuff');
    $responseObject->stuffArray[] = array( 'id'=>435, 'name'=>'Whatever stuff');
    $responseObject->stuffArray[] = array( 'id'=>65, 'name'=>'Cool Stuff');
    $responseObject->stuffArray[] = array( 'id'=>92, 'name'=>'Wow, what a stuff');          

    $responseObject->responseCode = 1;
    $responseObject->responseMessage = 'Successful!';
    return $responseObject; 
}

What's the appropriate NuSOAP code for that?
Thanks!! :)

I hope I could clarify what I would like to achieve: returning a struct which contains an int, a string and an array of structs, but don't know how to write the appropriate NuSOAP-code for that. This way I could firstly check the responseCode, and handle it with the appropriate error messages OR outputting the stuffArray, etc.

Answer

Sk8erPeter picture Sk8erPeter · Aug 9, 2011

After long hours of experimentation, I found the solution!

So giving back a structure containing three members - an int responseCode, a string responseMessage and an array of structs called stuffArray in the example - via SOAP with NuSOAP (PHP) looks like this below, I put some comments in the code to make it more unambiguous:

<?php

    class ResponseObject {
        public $responseCode = 0;
        public $responseMessage = 'Unknown error!';
        public $stuffArray = NULL;
    }   

    /**
     * @return object
     */ 
    function getStuffs( $user='', $pass='' ) {

        $responseObject = new ResponseObject();

        // check stuffs in a simple way now
        if( !($user == 'someone' and $pass == '1234') ){
            $responseObject->responseCode = 2;
            $responseObject->responseMessage = 'Authentication failed!';
            return $responseObject;
        }

        $responseObject->stuffArray   = array();
        $responseObject->stuffArray[] = array( 'id'=>122, 'name'=>'One stuff');
        $responseObject->stuffArray[] = array( 'id'=>213, 'name'=>'Another stuff');
        $responseObject->stuffArray[] = array( 'id'=>435, 'name'=>'Whatever stuff');
        $responseObject->stuffArray[] = array( 'id'=>65, 'name'=>'Cool Stuff');
        $responseObject->stuffArray[] = array( 'id'=>92, 'name'=>'Wow, what a stuff');          

        $responseObject->responseCode = 1;
        $responseObject->responseMessage = 'Successful!';
        return $responseObject; 
    }

    require_once 'nusoap/lib/nusoap.php';
    $server = new soap_server;

    // $myNamespace = $_SERVER['SCRIPT_URI'];
    $myNamespace = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];

    $server->configureWSDL(
        // string $serviceName, name of the service
        'MyStuffService',
        // mixed $namespace optional 'tns' service namespace or false
        // 'urn:' . $myNamespace
        $myNamespace
    );

    // $server->wsdl->schemaTargetNamespace = 'http://soapinterop.org/xsd/';
    $server->wsdl->schemaTargetNamespace = $myNamespace;

    $server->wsdl->addComplexType(
        // name
        'Stuffs',
        // typeClass (complexType|simpleType|attribute)
        'complexType',
        // phpType: currently supported are array and struct (php assoc array)
        'struct',
        // compositor (all|sequence|choice)
        'all',
        // restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
        '',
        // elements = array ( name = array(name=>'',type=>'') )
        array(
            'id' => array(
                'name' => 'id',
                'type' => 'xsd:int'
            ),
            'name' => array(
                'name' => 'name',
                'type' => 'xsd:string'
            )
        )
    );  

    $server->wsdl->addComplexType(
        // name
        'StuffsArray',
        // typeClass (complexType|simpleType|attribute)
        'complexType',
        // phpType: currently supported are array and struct (php assoc array)
        'array',
        // compositor (all|sequence|choice)
        '',
        // restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
        'SOAP-ENC:Array',
        // elements = array ( name = array(name=>'',type=>'') )
        array(),
        // attrs
        array(
            array(
                'ref' => 'SOAP-ENC:arrayType',
                'wsdl:arrayType' => 'tns:Stuffs[]'
            )
        ),
        // arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
        'tns:Stuffs'
    );


    $server->wsdl->addComplexType(
        // name
        'ResponseObject',
        // typeClass (complexType|simpleType|attribute)
        'complexType',
        // phpType: currently supported are array and struct (php assoc array)
        'struct',
        // compositor (all|sequence|choice)
        'all',
        // restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
        '',
        // elements = array ( name = array(name=>'',type=>'') )
        array
        (
            'responseCode' => array(   'type' => 'xsd:int'),
            'responseMessage' => array(   'type' => 'xsd:string'),
            'stuffArray'   => array(   'type' => 'tns:StuffsArray'
                                            // DON'T UNCOMMENT THE FOLLOWING COMMENTED LINES, BECAUSE THIS WAY IT DOESN'T WORK!!! - Left it in the code not to forget it....
                                            // ,
                                            // 'minOccurs' => '0',
                                            // 'maxOccurs' => 'unbounded'
                                            )
        )
    );

    $server->register(
        // string $name the name of the PHP function, class.method or class..method
        'getStuffs',
        // array $in assoc array of input values: key = param name, value = param type
        array(
            'user' => 'xsd:string',
            'pass' => 'xsd:string'
        ),
        // array $out assoc array of output values: key = param name, value = param type
        array(
            'return' => 'tns:ResponseObject'
        ),
        // mixed $namespace the element namespace for the method or false
        // 'urn:' . $myNamespace,
        $myNamespace,
        // mixed $soapaction the soapaction for the method or false
        // 'urn:' . $myNamespace . "#getStuffs",
        $myNamespace . "#getStuffs",
        // mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
        'rpc',
        // mixed $use optional (encoded|literal) or false
        'encoded',
        // string $documentation optional Description to include in WSDL
        'Fetch array of Stuffs ("id", "name").' // documentation
    );

    // $server->wsdl->schemaTargetNamespace = $myNamespace;

    // function def.: nusoap/lib/class.soap_server.php (236)
    $server->service(isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '');

    //   DON'T UNCOMMENT THE FOLLOWING LINES!! - Don't call these headers explicitly,
    //   everything will be handled in function service() appropriately - I know it by experience that it's not a good choice...
    // output:wsdl
    // header('Content-Type: text/xml;charset=utf-8');
    // header('Content-Type: text/xml');
    // echo $server->wsdl->serialize();

    exit(0);

Give this file a name, for example getStuffComplex.php, and then copy this file somewhere on your webserver, and remember its path.
For example one domain name on my local webserver is http://soap.local, and the above mentioned PHP-code can be reached at http://soap.local/getStuffComplex.php.

Let's say you want to call getStuffs() function in a C# code via a SOAP client, from a Console Application under Visual Studio 2010. In this case you have to do the following steps:

  1. Create a new Console Application project
  2. Right click "References" - "Add Service Reference"
  3. Click "Advanced..."
  4. Click "Add Web Reference..."
  5. Paste the path of the previously saved PHP-file's URL (with the content above) and append the "?wsdl" string in the URL field. For example in my case: http://soap.local/getStuffComplex.php?wsdl
  6. Click the green right arrow ("Go") or hit Enter after fill out URL field. If getStuff() method is found, the situation is hopeful.
  7. Give the reference a name on the right side (Web reference name), for example "StuffServiceComplex" (I will use this name in my code), than hit Enter. Now you have to see it under "Web References".
  8. Copy the code below into Program.cs, and test it by hitting F5 or clicking the green "play" icon.

The C# code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services.Protocols;

namespace WebServiceTestComplex
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                StuffServiceComplex.MyStuffService myService = new StuffServiceComplex.MyStuffService();

                StuffServiceComplex.ResponseObject myRespObject = myService.getStuffs("someone", "1234");

                switch (myRespObject.responseCode)
                {
                    // Everything was OK, results can be output
                    case 1:
                        Console.WriteLine("Everything's OK, let's write the results to the standard output:");
                        foreach (var stuff in myRespObject.stuffArray)
                        {
                            Console.WriteLine("\t"+stuff.id + ".:\t" + stuff.name);
                        }
                        break;
                    // Authentication failed
                    case 2:
                    // Unknown error
                    case 0:
                    default:
                        Console.WriteLine("Error:");
                        Console.WriteLine("\tError code: "+myRespObject.responseCode);
                        Console.WriteLine("\tError message: " + myRespObject.responseMessage);
                        break;
                }

            }
            catch (SoapException e)
            {
                Console.WriteLine("=== SOAP EXCEPTION!! ===");
                Console.WriteLine(e);
            }
            catch (Exception e)
            {
                Console.WriteLine("=== OTHER EXCEPTION!! ===");
                Console.WriteLine(e.ToString());
            }


            Console.WriteLine();
            Console.WriteLine("Press a key...");
            Console.ReadKey();
        }
    }
}

The output:

Everything's OK, let's write the results to the standard output:
        122.:   One stuff
        213.:   Another stuff
        435.:   Whatever stuff
        65.:    Cool Stuff
        92.:    Wow, what a stuff

Press a key...

I hope this helps someone who struggled with bringing PHP and SOAP and .NET together.

(Note: take care about character coding when using accents or any special letters. By default, ANSI could be used (but character encodings have to be the same).)