Validate SOAP request by generating XML Schema from Zend Soap AutoDiscover WSDL
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.xmlThe 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>