You're in the army now
Getting squeakly clean with Zend_Soap
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.
| Print article | This entry was posted by Matt Cockayne on September 15, 2009 at 2:06 pm, and is filed under Spec Ops. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |



about 9 months ago
I may be wrong here, but when using Zend_Soap_Client to consume a .NET web-service, I had to set the soap_version option to the SOAP_1_1 constant.
Is it possible .NET services (in specific versions?) use SOAP 1.1 by default (Zend_Soap_Client uses 1.2 by default)?
I’m not a SOAP expert, but this may be the cause of your incompatibilities too?
about 9 months ago
Not quite… my problems were occuring because a .NET client was trying to consume a service created with the Zend_Soap_Server in conjunction with autodiscover.
I have used the Zend_Soap_Client to consume a number of .NET based services since I wrote this post without the need to change the soap version
I think that the version of soap used by .NET is dependent upon the version of .NET used and the libraries used to generate the service.
Good tip to know for future reference though, thanks.