So I have this PEAR::SOAP server that I'm trying to get working with a C# client. My problem is that I can't get the client to accept back an array of values the way that PEAR::SOAP sends them. I know this isn't a C# board, but I was hoping that someone might have run into this, or know how I can change the way PEAR::SOAP returns an array.

Currently my server returns XML like this:
<return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="Struct[4]" SOAP-ENC:offset="[0]">

What I think I need to have returned is something like this:

<return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:Stuff[4]" SOAP-ENC: offset="[0]">

This version shows that I'm returning an array of values that conform to my Stuff type. I've already got Stuff defined and can return singles of it, and my array is suppose to hold these objects so how do I make PEAR::SOAP know that this is what I'm returning?

    10 months later

    Hi ,
    Just wanted to know you if you solved this problem and if so how? I have written a java client in Axis which uses a PHP webservice that returns an array ; The SOAP Response comes back with arrayType "Struct" which is not recognized by my client; I am not a PHP programmer. I want to help the PHP programmer who created the webservice; Any solution you developed will be useful. I appreciate your help.

      Wow, that's an old question. Yes I solved it, but now I've got to go investigate how I solved it.

      Ok, here is what I did. I created a new user defined type, mine happend to be called ObjectList, which was an array of my Objects.

      So in my server object I had these 2 objects defined:

      $this->namespace = "ObjectManagement";
      $this->__typedef["Object"] = array('ObjectID'           => 'unsignedInt',
                             'ObjectNumber'       => 'unsignedInt',
                             'TimeZone'          => 'string');
      
      $this->__typedef["ObjectList"] = array(array('Object' => '{' . $this->namespace . '}Object')
      

      Note that I had to put the namespace of my service infront of the name of my Object, this is what I was originally missing. This forces my WSDL to ouput the correct information telling my client that my array is of type object.

      <complexType name="ObjectList">
         <complexContent>
            <restriction base="SOAP-ENC:Array">
               <attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="tns:Object[]" /> 
            </restriction>
         </complexContent>
      </complexType>
      

      I hope this helps. This isn't a problem with PEAR::SOAP it's a problem with the documentation on SOAP that is out there.

        5 days later

        Thanks. I will ask the php developer to look into your answer.

          Thanks! I've bookmarked this thread. That is useful information.

          However, for some strange reason it seems to not work with zero-argument functions. The returned XML contains arrayType="Struct[27]" instead of arrayType="YourType[27]". The MS clients won't consume that.

          Any known workarounds, other than requiring an argument?

            I don't understand your question. What do you mean by zero argument functions?

            The number of arguments that your function takes shouldn't have any effect on the information that is returned by your function. Maybe you should post an example.

              The number of arguments shouldn't have any effect on the format of the returned XML, but it clearly does.

              I'll whip up an example shortly.

                Since you haven't seen this I guess you don't know of a workaround, but I'll post an example anyway.

                Here's the server:

                <?php
                include_once('SOAP/Server.php');
                
                class WebService {
                        var $__dispatch_map = array();
                        var $__typedef = array();
                
                    function WebService() {
                            $this->__typedef['{urn:WebService}MyType'] = array(
                             'foo' => 'int',
                             'bar' => 'string',
                            );
                            $this->__typedef['{urn:WebService}MyTypeArray'] = array(array('MyType' => '{urn:WebService}MyType'));
                            $this->__dispatch_map['test1'] = array(
                             'in' => array(),
                             'out' => array('things' => '{urn:WebService}MyTypeArray'),
                            );
                            $this->__dispatch_map['test2'] = array(
                             'in' => array('dummy' => 'int'),
                             'out' => array('things' => '{urn:WebService}MyTypeArray'),
                            );
                    }
                
                    // Required function by SOAP_Server
                    function __dispatch($methodname) {
                            if (isset($this->__dispatch_map[$methodname])) {
                                    return $this->__dispatch_map[$methodname];
                            }
                            else {
                                    return NULL;
                            }
                    }
                
                    function test1() {
                            $a = array('foo' => 3, 'bar' => 'Five!');
                            return array($a, $a, $a);
                    }
                
                    function test2($dummy) {
                            $a = array('foo' => 3, 'bar' => 'Five!');
                            return array($a, $a, $a);
                    }
                }
                
                // Fire up PEAR::SOAP_Server
                $server =& new SOAP_Server();
                
                // Add object to SOAP server
                $ws =& new WebService();
                $server->addObjectMap($ws, 'urn:WebService');
                
                // Handle SOAP requests coming is as POST data
                if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD']=='POST') {
                        $server->service($HTTP_RAW_POST_DATA);
                }
                else {
                        // Deal with WSDL / Disco here
                        require_once 'SOAP/Disco.php';
                
                    // Create the Disco server
                    $disco = new SOAP_DISCO_Server($server, 'WebService');
                    header("Content-type: text/xml");
                    if (isset($_SERVER['QUERY_STRING']) && strcasecmp($_SERVER['QUERY_STRING'],'wsdl')==0) {
                            echo $disco->getWSDL(); // if we're talking [url]http://www.example.com/index.php?wsdl[/url]
                    }
                    else {
                            // Disco
                            echo $disco->getDISCO();
                    }
                    exit;
                }
                
                ?>
                

                And here's the client:

                <?php
                include_once("SOAP/Client.php");
                header("Content-type: text/plain");
                $wsdl = new SOAP_WSDL('http://www.example.net/WebService.php?WSDL');
                $soapclient = $wsdl->getProxy();
                $soapclient->setOpt('trace', 1);
                echo "This is calling test1():\n\n";
                $soapclient->test1();
                echo $soapclient->__last_response;
                echo "\n\n\n\n";
                echo "This is calling test2():\n\n";
                $soapclient->test2(0);
                echo $soapclient->__last_response;
                ?>
                

                And here is the output when the client is called:

                This is calling test1():
                
                HTTP/1.1 200 OK
                Date: Thu, 11 Mar 2004 23:06:27 GMT
                Server: Apache/1.3.27 OpenSSL/0.9.7 (Unix) PHP/4.3.4
                X-Powered-By: PHP/4.3.4
                Status: 200 OK
                Content-Length: 847
                Connection: close
                Content-Type: text/xml; charset=UTF-8
                
                <?xml version="1.0" encoding="UTF-8"?>
                
                <SOAP-ENV:Envelope  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:ns4="urn:WebService"
                 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                <SOAP-ENV:Body>
                
                <ns4:test1Response>
                <return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="Struct[3]" SOAP-ENC:offset="[0]">
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five!</bar></item>
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five!</bar></item>
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five!</bar></item></return></ns4:test1Response>
                </SOAP-ENV:Body>
                </SOAP-ENV:Envelope>
                
                
                
                
                This is calling test2():
                
                HTTP/1.1 200 OK
                Date: Thu, 11 Mar 2004 23:06:28 GMT
                Server: Apache/1.3.27 OpenSSL/0.9.7 (Unix) PHP/4.3.4
                X-Powered-By: PHP/4.3.4
                Status: 200 OK
                Content-Length: 848
                Connection: close
                Content-Type: text/xml; charset=UTF-8
                
                <?xml version="1.0" encoding="UTF-8"?>
                
                <SOAP-ENV:Envelope  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:ns4="urn:WebService"
                 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                <SOAP-ENV:Body>
                
                <ns4:test2Response>
                <return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="ns4:MyType[3]" SOAP-ENC:offset="[0]">
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five</bar></item>
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five</bar></item>
                <item>
                <foo xsi:type="xsd:int">3</foo>
                <bar xsi:type="xsd:string">Five</bar></item></return></ns4:test2Response>
                </SOAP-ENV:Body>
                </SOAP-ENV:Envelope>
                

                When test1 is called, the return arrayType is Struct[3], which is wrong. When test2 is called, the return arrayType is MyType[3], which is right.

                This is using PEAR::SOAP 0.8 RC2.

                  Thats very strange. Everything looks right.

                  Well here is some of my code that works to return an array even from a zero argument function call.

                  This is a function that is in the base class (ServiceBase) for all of my SOAP Server classes.

                     /**
                     * Builds a proper SOAP_Value object for sending by the PEAR::SOAP Server.
                     *
                     * Takes in an associative array and returns a properly formed
                     * SOAP_Value object.  Walks through each member of the given $value to see
                     * if it is a simple SOAP type, a user defined type, or an array.  Then 
                     * depending on what the variable type is builds the appropriate SOAP_Value
                     * object.  This method is recursive so it handles user defined types that 
                     * have fields of other user defined types. 
                     *
                     * @param	string	$name	The name of the variable to be created. (i.e.
                     *                            the name of an out variable in a SOAP API, or
                     *                            the name of a field in a user type).
                     * @param	mixed	$value	An associative array that maps to a user
                     *                            defined variable type or a simple value that
                     *                            matches a SOAP predefined variable type.
                     * @param	string	$type	The type of the SOAP_Value to create.
                     * @return	SOAP_Value
                     */
                     function buildSOAP($name, $value, $type) {
                        $retVal = NULL;
                  
                    if (isset($this->__typedef[$type])) {
                       foreach($value as $subItemName => $subItemValue) {
                          if ($subItemValue != NULL) {
                             if (is_array($this->__typedef[$type][0])) {
                                $arrayItemName = key($this->__typedef[$type][0]);
                                $arrayItemType =
                                   $this->stripNamespace(
                                      $this->__typedef[$type][0][$arrayItemName]);
                  
                                $retVal[] = $this->buildSOAP($arrayItemName,
                                                             $subItemValue,
                                                             $arrayItemType);
                             } else {
                                $subItemType = $this->stripNamespace(
                                                  $this->__typedef[$type][$subItemName]);
                                if (!is_numeric($subItemName) &&
                                    isset($this->__typedef[$type][$subItemName])) {
                  
                                   if ($retVal != NULL) { 
                                      if (is_array($retVal)) {
                                         $retVal[] = $this->buildSOAP($subItemName,
                                                                      $subItemValue,
                                                                      $subItemType);
                                      } else {
                                         $temp = $retVal;
                                         $retVal = array();
                                         $retVal[] = $temp;
                  
                                         $retVal[] = $this->buildSOAP($subItemName,
                                                                      $subItemValue,
                                                                      $subItemType);
                                      }
                                   } else {
                                      $retVal = $this->buildSOAP($subItemName,
                                                                 $subItemValue,
                                                                 $subItemType);
                                   }
                                }
                             }
                          }
                       }
                       if ($retVal != NULL) {
                          $retVal = new SOAP_Value($name,
                                                   '{' . $this->namespace . '}' . $type,
                                                   $retVal);
                       }
                    } else {
                       $retVal = new SOAP_Value($name, 
                                                $type,
                                                $value);
                    }
                    return $retVal;
                     }
                  

                  Here is a Server class that works:

                  require_once('../Library/ServiceBase.php');
                  require_once('../Library/webservice.php');
                  
                     class IWSService extends ServiceBase {
                  
                    function IWSService() {
                       parent::ServiceBase('IWS', IWS_USERNAME, IWS_PASSWORD);
                  
                       $this->addMap('GetAvailableServices',
                          array('in' => array(),
                                'out' => array('ServiceList' => '{'
                                                              . $this->namespace
                                                              . '}ServiceList')));
                  
                       $this->addTypeDef('Service',
                          array('Name' => 'string',
                                'URL' => 'string'));
                  
                       $this->addTypeDef('ServiceList',
                          array(array('Service' => '{' . $this->namespace . '}Service')));
                    }
                  
                    function GetAvailableServices() {
                       $services = array();
                  
                       // Protocol
                       if ($_SERVER['HTTPS'] == 'on') {
                          $protocol = 'https://';  
                       } else {
                          $protocol = 'http://';
                       }
                  
                       // Port
                       if ($_SERVER['SERVER_PORT'] != 80) {
                          $port = ':' . $_SERVER['SERVER_PORT'];
                       } else {
                          $port = '';
                       }
                  
                       // Default IWS Service
                       $url = $protocol . $_SERVER['SERVER_ADDR'] . $port
                            . substr($_SERVER['REQUEST_URI'], 0,
                                     strrpos($_SERVER['REQUEST_URI'], '/') ) . '/';
                       $services[] = array('Name' => 'IWS',
                                           'URL' => $url);
                  
                       $dh = opendir('.');
                  
                       while (($file = readdir($dh)) !== false) {
                          if ($file != '.' && $file != '..') {
                             if (is_dir($file)) {
                                $url = $protocol . $_SERVER['SERVER_ADDR'] . $port
                                     . substr($_SERVER['REQUEST_URI'], 0,
                                              strrpos($_SERVER['REQUEST_URI'], '/') )
                                     . '/' . $file . '/';
                  
                                if (($client = WebService::ServiceProxy($url . '?wsdl'))
                                     != false) {
                  
                                   if (is_object($client)) {
                                      $name = $client->Identify();
                                      $services[] = array('Name' => $name, 
                                                          'URL'  => $url);
                                   }
                                }
                             }
                          }
                       }
                       return $this->buildSOAP('ServiceList',
                                               $services,
                                               'ServiceList');
                    }
                     }
                  

                  In this setup I buid a SOAP_Value object that I return, this was another issue I found with PEAR::SOAP in that for complex types it didn't always do a good job of converting my types into valid objects that would be interpreted by C#.

                  Let me know if this helps or not.

                    Write a Reply...