Validate SOAP request by generating XML Schema from Zend Soap AutoDiscover WSDL

From FVue
Jump to: navigation, search

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. MyCall accepts parameter MyCallRequest
  • 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>