Using Complex Type with Zend_Soap

To be able to use complex types with Soap requests, they need to be fully defined in the WSDL file. Zend_Soap can automate this process, if you know how to define those complex types. Let us start without it Zend_Soap's magic and compare it with a fully discovered complex request type afterwards.

First, let's define your classes. Let's assume we have a book collection, and each book can have multiple tags.

class Book { public $title; public $author; public $tags; } class Tag { public $tag; }

Our webservice class looks like this:

class Webservice { public function insertBook($book) { return true; } }

By using Zend_Soap and the automagically created WSDL file, the code launching our SOAP server could look like this:

ini_set("soap.wsdl_cache_enabled", 0);//for development if (isset($_GET['WSDL'])) { $autodiscover = new Zend_Soap_AutoDiscover(); Zend_Loader::loadClass('Webservice'); $autodiscover->setClass('Webservice'); $autodiscover->setUri('http://localhost/soap.php'); $autodiscover->handle(); } else { $options = array('soap_version' => SOAP_1_2); Zend_Loader::loadClass('Webservice'); $server = new Zend_Soap_Server('http:/localhost/soap.php?WSDL=1', $options); $server->setObject(new Webservice()); $server->handle(); }

The WSDL (just browse to http:/localhost/soap.php?WSDL=1) does not hold any information about the Book or Tags objects:

... <portType name="WebservicePort"> <operation name="insertBook"> <documentation>insertBook</documentation> <input message="tns:insertBookIn"/> </operation> </portType> .. <message name="insertBookIn"> <part name="book" type="xsd:anyType"/> </message> ...

The inserted book can be of anyType. This is not really useful as you can imagine.

Now, let's spice things up. The real magic in Zend_Soap is in the autodiscovery mechanism producing the WSDL file. Zend_Soap_AutoDiscover won't know how to use all those variables, unless you define the types with correct docblock information, like this:

class Book { /** @var string */ public $title; /** @var string */ public $author; /** @var Tag[] */ public $tags; } class Tag { /** @var string */ public $tag; }

The type for $tags is Tag[], Tag comes from the defined class, and [] makes it an array. Note the /**, it's necessary to use 2 asterisks for the Reflection class to identify the docblock.

Now you can use the Book type in your Webservice functions. The insertBook method is annotated like this:

class Webservice { /** * * @param Book $book * @return bool */ public function insertBook($book) { // here comes code to insert your new book into the database // $book is of type Book, with an array of Tag in it return true; } }

Zend_Soap_Autodiscover has several strategies, you can read the Zend Framework Manual for more information on complex type strategies. We will use Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex, so Autodiscover can find the Tag array in the Book object. To do so, just update this line:

$autodiscover = new Zend_Soap_AutoDiscover();

to this:

$autodiscover = new Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex');

The WSDL (download here) will now hold a lot more information. Custom types were added for example:

... <types> <xsd:schema targetNamespace="http://localhost/soap.php"> <xsd:complexType name="ArrayOfTag"> <xsd:complexContent> <xsd:restriction base="soap-enc:Array"> <xsd:attribute ref="soap-enc:arrayType" wsdl:arrayType="tns:Tag[]"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="Tag"> <xsd:all> <xsd:element name="tag" type="xsd:string"/> </xsd:all> </xsd:complexType> <xsd:complexType name="Book"> <xsd:all> <xsd:element name="title" type="xsd:string"/> <xsd:element name="author" type="xsd:string"/> <xsd:element name="tags" type="tns:ArrayOfTag"/> </xsd:all> </xsd:complexType> </xsd:schema> </types> <portType name="WebservicePort"> <operation name="insertBook"> <documentation>@param Book $book</documentation> <input message="tns:insertBookIn"/> <output message="tns:insertBookOut"/> </operation> </portType> ... <message name="insertBookIn"> <part name="book" type="tns:Book"/> </message> <message name="insertBookOut"> <part name="return" type="xsd:boolean"/> </message> ...

The insertBookIn message now mentions the type Book. And Book is described in the types container in detail. Web service clients are now able to insert books with all information as required by the service.

Remember, the real magic of the Zend_Soap component lies in Zend_Soap_AutoDiscover. You can even create the WSDL file using Zend_Soap_AutoDiscover and then expose the service using PHP-only methods. You just need an additional classmap to make this possible:

class Book { /** @var string */ public $title; /** @var string */ public $author; /** @var Tag[] */ public $tags; } class Tag { /** @var string */ public $tag; } class Webservice { /** * * @param Book $book * @return bool */ public function insertBook($book) { return true; } } $classmap = array('Book' => 'Book','Tag' => 'Tag'); $server = new SoapServer('bookservice.wsdl',array('classmap' => $classmap)); $server->setClass("Webservice"); $server->handle();

Want to know more? Read more on Web Services and Zend Framework in the Zend Framework Web Services book, available from PHP|Architect and written by Jonas.

King Foo is a SensioLabs partner