Validate SOAP request by generating XML Schema from Zend Soap AutoDiscover WSDL
Contents
Problem
I have Zend-Soap auto-discovering a WSDL from my PHP code. Now I want to validate an incoming SOAP request, using this WSDL which has XML Schema embedded. Can I generate a working XML Schema from the generated WSDL?
Environment
- ZendFramework-1.12.3
- xmllint: using libxml version 20708
- xsltproc: using libxml 20708, libxslt 10126 and libexslt 815
Solution
Given that:
- the call parameter is suffixed with
Request, e.g.MyCallaccepts parameterMyCallRequest - a default namespace attribute is added to the request soap:Envelope element, e.g.
xmlns="http://my.soap.server"
you can use the wsdl2xsd.xslt underneath to convert the WSDL to XML Schema, using this command:
xsltproc wsdl2xsd.xslt soapapi.wsdl > soapapi.xsd~Validation goes like this:
xmllint --noout --schema soapapi.xsd~ request.xml
The wsdl2xsd.xslt:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="xsd:schema"> <xsd:schema elementFormDefault="qualified" targetNamespace="http://my.soap.server" xmlns:tns="http://my.soap.server" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" schemaLocation="http://schemas.xmlsoap.org/soap/encoding/"/> <xsd:import namespace="http://schemas.xmlsoap.org/soap/envelope/" schemaLocation="http://schemas.xmlsoap.org/soap/envelope/"/> <xsl:apply-templates select="*"/> <xsl:apply-templates select="/wsdl:definitions/wsdl:portType/*"/> </xsd:schema> </xsl:template> <xsl:template match="/"> <xsl:apply-templates select="wsdl:definitions"/> </xsl:template> <xsl:template match="wsdl:definitions"> <xsl:apply-templates select="wsdl:types"/> </xsl:template> <xsl:template match="wsdl:types"> <xsl:apply-templates select="xsd:schema"/> </xsl:template> <xsl:template match="wsdl:portType/wsdl:operation"> <xsl:element name="xsd:element"> <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute> <xsd:complexType> <xsd:all> <xsd:element> <xsl:attribute name="name"><xsl:value-of select="@name"/>Request</xsl:attribute> <xsl:attribute name="type">tns:<xsl:value-of select="@name"/>Request</xsl:attribute> </xsd:element> </xsd:all> </xsd:complexType> </xsl:element> </xsl:template> </xsl:stylesheet>
Caveat: minOccurs and maxOccurs not supported
The XML Schema properties minOccurs and maxOccurs are not supported by Zend_Wsdl_Autodiscovery. I applied the patch underneath to support this PHPDoc comment:
/**
* @var string minOccurs=0 maxOccurs=1
*/The patch:
--- DefaultComplexType.orig.php 2014-02-14 15:58:08.873920009 +0100 +++ DefaultComplexType.php 2014-02-14 15:17:02.733920020 +0100 @@ -73,9 +73,23 @@ $element->setAttribute('name', $propertyName = $property->getName()); $element->setAttribute('type', $this->getContext()->getType(trim($matches[1][0]))); - // If the default value is null, then this property is nillable. - if ($defaultProperties[$propertyName] === null) { - $element->setAttribute('nillable', 'true'); + $doc = $property->getDocComment(); + $minOccurs = $maxOccurs = null; + if (preg_match('/minOccurs\s*=\s*(\d+|unbounded)/', $doc, $matches)) { + $element->setAttribute('minOccurs', $minOccurs = $matches[1]); + } + if (preg_match('/maxOccurs\s*=\s*(\d+|unbounded)/', $doc, $matches)) { + $element->setAttribute('maxOccurs', $maxOccurs = $matches[1]); + } + + // If minOccurs > 0, then nillable is not allowed + if (is_numeric($minOccurs) && $minOccurs > 0) { + $element->setAttribute('nillable', 'false'); + } else { + // If the default value is null, then this property is nillable. + if ($defaultProperties[$propertyName] === null) { + $element->setAttribute('nillable', 'true'); + } } $all->appendChild($element);
See also:
- http://www.zendframework.com/issues/browse/ZF-10004
- Proposed code changes for ArrayOfTypeSequence. Example notation: "@var string[0,5] $stringArray". No patch files found?
- http://framework.zend.com/issues/browse/ZF-9180?page=com.atlassian.jira.plugin.system.issuetabpanels%3achangehistory-tabpanel
- Example notation: "@var string maxOccurs=1 minOccurs=1". No patch files found?
- http://zend-framework-community.634137.n4.nabble.com/Zend-Soap-Autodiscover-how-to-generate-minOccurs-and-maxOccurs-in-WSDL-td675932.html
- Example notation: "@var string ___FOR_ZEND_minOccurs=1 ___FOR_ZEND_maxOccurs=1"
Journal
20140211
The generated WSDL has the XML Schema defined with the /definitions/types element. These can be copied verbatim into the template underneath.
Furthermore s template, replace my.soap.server with yours:
<?xml version="1.0"?> <xsd:schema targetNamespace="http://my.soap.server" xmlns:tns="http://my.soap.server" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" schemaLocation="http://schemas.xmlsoap.org/soap/encoding/"/> <xsd:import namespace="http://schemas.xmlsoap.org/soap/envelope/" schemaLocation="http://schemas.xmlsoap.org/soap/envelope/"/> ... <!-- /definitions/types/* copied here --> <!-- Insert extra elements to map the input parameters --> <xsd:element name="MyCall"> <xsd:complexType> <xsd:all> <xsd:element name="MyCallRequest" type="tns:MyCallRequest" /> </xsd:all> </xsd:complexType> </xsd:element>