I recently had to set up a SOAP service for a client. So I thought it was the perfect opportunity to get to grips with the Zend_Soap_Server.

Everything went swimmingly. I agreed on a structure with the customer and following the Zend Framework reference manual i managed to set up the server in no time at all using the auto discover feature.

Now I use a linux environment and tested the service with php_soap, nusoap and Zend_Soap_Client, and it all worked perfectly. it was so easy to use i found myself laughing.

But…. and here is the killer. Its only after I spent two whole days writing and testing the service that I hand it over to the customer and… it doesn’t work.

Turns out that the dotnet soap client doesnt like the doesnt like the dataType “struct”.

So I break out my windows box and test with a couple of windows clients (SoapUI and Liquid Studio) surprisingly they also had some errors. they kept falling over at the “struct” dataType.

A look at the WSDL generation was called for. Turns out that Zend_Soap_Wsdl uses a simple switch to define the data types.

 public function getType($type)
    {
        switch (strtolower($type)) {
            case 'string':
            case 'str':
                return 'xsd:string';
                break;
            case 'int':
            case 'integer':
                return 'xsd:int';
                break;
            case 'float':
            case 'double':
                return 'xsd:float';
                break;
            case 'boolean':
            case 'bool':
                return 'xsd:boolean';
                break;
            case 'array':
                return 'soap-enc:Array';
                break;
            case 'object':
                return 'xsd:struct';
                break;
            case 'mixed':
                return 'xsd:anyType';
                break;
            case 'void':
                return '';
            default:
                // delegate retrieval of complex type to current strategy
                return $this->addComplexType($type);
            }
    }

all it took was a quick change to the ‘object’ case and hey presto it worked on my windows clients without an issue.

            case 'object':
                return 'soap-enc:Struct';
                break;

So I though that all my troubles were over.

Turns out I was wrong.

Now bear in mind that I’m not a dotnet developer and dont pretend to be. I have a good Idea how its all supposed to work and I can Kinda get the gist of whats happening from the code. However it still took me another 4 hours to realise that when the dotnet client consumes the wsdl it creates a whole load of additional rules that it validates the returned result by.

It turned out that it received the result successfully but it just wouldn’t parse it.

So back to the drawing board. In the end it was a case of using a hand built wsdl file with all the right structures being defined inside the file rather than relying on the standard dataTypes within Zend_Soap. Bit of a shame but it guarantees the service to work with all clients regardless.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<xsd:complexType name="Response_Item_Address">
    <xsd:all>
        <xsd:element name="recipient" type="xsd:string"/>
        <xsd:element name="line1" type="xsd:string"/>
        <xsd:element name="line2" type="xsd:string"/>
        <xsd:element name="line3" type="xsd:string"/>
        <xsd:element name="line4" type="xsd:string"/>
        <xsd:element name="postcode" type="xsd:string"/>
        <xsd:element name="country" type="xsd:string"/>
    </xsd:all>
</xsd:complexType>
<xsd:complexType name="Response_Item">
    <xsd:all>
        <xsd:element name="id" type="xsd:int"/>
        <xsd:element name="md5" type="xsd:string"/>
        <xsd:element name="uri" type="xsd:string"/>
        <xsd:element name="barcode" type="xsd:string"/>
        <xsd:element name="type" type="xsd:string"/>
        <xsd:element name="quantity" type="xsd:int"/>
        <xsd:element name="size" type="xsd:string"/>
        <xsd:element name="deliveryDate" type="xsd:string"/>
        <xsd:element name="envelopeMessage" type="xsd:string"/>
        <xsd:element name="deliveryType" type="xsd:string"/>
        <xsd:element name="status" type="xsd:string"/>
        <xsd:element name="address" type="tns:Response_Item_Address"/>
    </xsd:all>
</xsd:complexType>
<xsd:complexType name="Get_Request_Params">
    <xsd:all>
        <xsd:element name="date" type="xsd:string"/>
        <xsd:element name="batch" type="xsd:int"/>
    </xsd:all>
</xsd:complexType>
<xsd:complexType name="Get_Request">
    <xsd:all>
        <xsd:element name="params" type="tns:Whamoosh_Soap_Cards_GetFuture_Request_Params"/>
    </xsd:all>
</xsd:complexType>
<xsd:complexType name="Get_Response">
    <xsd:complexContent>
        <xsd:restriction base="soap-enc:Array">
            <xsd:attribute ref="soap-enc:arrayType" wsdl:arrayType="tns:Response_Item[]"/>
        </xsd:restriction>
    </xsd:complexContent>
</xsd:complexType>

Nothing like a well defined complexType structure..

Much as I like the autoDiscover for Zend Soap its still a long way of being able to handle stupidly complex structures to generate the wsdl.

The compromise is that I could still use the exact same structure to process the SOAP requests but just output a manually written wsdl file.

Best of both worlds

public function indexAction()
    {
   		if(isset($_GET['wsdl'])) {
            //return the WSDL
            $this->handleWSDL();
        } else {
            //handle SOAP request
            $this->handleSOAP();
        }
    }

    /**
     * deliver hand coded wsdl file
     * @return unknown_type
     */
    private function handleWSDL()
    {
	$wsdl = file_get_contents(APPLICATION_PATH.'/wsdl/card.wsdl');
    	if (!headers_sent()) {
            header('Content-Type: text/xml');
        }
	echo $wsdl;
    }

    private function handleSOAP()
    {
    	$soap = new Zend_Soap_Server($this->_WSDL_URI);
        $soap->setClass($this->_handler);
        $soap->handle();
    }

This may not be the most informative/helpful post in the world but I’m happy to offer advice if someone else has problems like mine.