WSDL is an XML format for describing web services. Constructing PHP clients from WSDL is no problem but as WSDL is uses XML schema (which is effectively strongly typed), the relationship between PHP and WSDL on the server side is not so fine.
Rather than generating WSDL from a PHP server, this is an experiment in generating PHP from WSDL, with help from a little XSLT.
1) The objective is to provide a foundation for rapid development and deployment of web services using PHP. Wouldn’t it be nice if building a web service was just a case of creating a WSDL document, run a quick XSL transform on it and “hey presto” the PHP is automatically generated for us in a form that’s easy to plug in to an existing application.
This comes as a follow up to working on Wrox’s upcoming Professional PHP Web Services, but was something too experimental for the books deadline.
It’s here purely as a proof of concept (i.e. aside from generating some PHP from a WSDL document, this hasn’t resulted in a working web service yet).
Take one WSDL document such as;
<?xml version="1.0"?>
<!-- Example wsdl document: Uche Ogbuji's (uche@fourthought.com) demo
of a Snowboarding service from
http://www-106.ibm.com/developerworks/webservices/library/ws-soap/code.html -->
<definitions name="EndorsementSearch"
targetNamespace="http://namespaces.snowboard-info.com"
xmlns:es="http://www.snowboard-info.com/EndorsementSearch.wsdl"
xmlns:esxsd="http://schemas.snowboard-info.com/EndorsementSearch.xsd"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<schema targetNamespace="http://namespaces.snowboard-info.com"
xmlns="http://www.w3.org/1999/XMLSchema">
<element name="GetEndorsingBoarder">
<complexType>
<sequence>
<element name="manufacturer" type="string"/>
<element name="model" type="string"/>
</sequence>
</complexType>
</element>
<element name="GetEndorsingBoarderResponse">
<complexType>
<all>
<element name="endorsingBoarder" type="string"/>
</all>
</complexType>
</element>
<element name="GetEndorsingBoarderFault">
<complexType>
<all>
<element name="errorMessage" type="string"/>
</all>
</complexType>
</element>
</schema>
</types>
<message name="GetEndorsingBoarderRequest">
<part name="body" element="esxsd:GetEndorsingBoarder"/>
</message>
<message name="GetEndorsingBoarderResponse">
<part name="body" element="esxsd:GetEndorsingBoarderResponse"/>
</message>
<portType name="GetEndorsingBoarderPortType">
<operation name="GetEndorsingBoarder">
<input message="es:GetEndorsingBoarderRequest"/>
<output message="es:GetEndorsingBoarderResponse"/>
<fault message="es:GetEndorsingBoarderFault"/>
</operation>
</portType>
<binding name="EndorsementSearchSoapBinding"
type="es:GetEndorsingBoarderPortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="GetEndorsingBoarder">
<soap:operation
soapAction="http://www.snowboard-info.com/EndorsementSearch"/>
<input>
<soap:body use="literal"
namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
</input>
<output>
<soap:body use="literal"
namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
</output>
<fault>
<soap:body use="literal"
namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
</fault>
</operation>
</binding>
<service name="EndorsementSearchService">
<documentation>snowboarding-info.com Endorsement Service</documentation>
<port name="GetEndorsingBoarderPort"
binding="es:EndorsementSearchSoapBinding">
<soap:address
location="http://www.snowboard-info.com/EndorsementSearch"/>
</port>
</service>
</definitions>
Now apply an XSL stylesheet, perhaps with a little help from Sablotron and Luis Argerich PHP XML Classes or XML Cooktop
<?xml version="1.0" ?> <?xml-stylesheet href="wsdl2php.xslt" type="text/xsl"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"> <xsl:output method="text" indent="no"/> <!-- Create PHP tags --> <xsl:template match="/"><?php <xsl:apply-templates /> ?> </xsl:template> <xsl:template match="comment()" > /** <xsl:value-of select="." /> */ </xsl:template> <!-- Ignore documentation --> <xsl:template match="wsdl:documentation" /> <!-- Define base class --> <xsl:template match="wsdl:definitions" > <xsl:call-template name="documentation"/> /** Abstract web service class */ class WebService { var $dao // Data access object var $dispatchMap=array(); // SOAP Dispatch map // CONSTRUCTOR function WebService (& $dao) { $this->dao=& $dao; } } <xsl:apply-templates select="wsdl:portType"/> </xsl:template> <!-- Extend WebService with child class for each portType --> <xsl:template match="wsdl:portType" > <xsl:call-template name="documentation"/> class <xsl:value-of select="@name"/> extends WebService { // CONSTRUCTOR function <xsl:value-of select="@name"/> (& $dao) { WebService::WebService($dao); // Register dispatch map <xsl:for-each select="wsdl:operation"> <xsl:call-template name="dispatch"/></xsl:for-each> } <xsl:apply-templates /> } </xsl:template> <!-- Build class method for each operation --> <xsl:template match="wsdl:operation" > <xsl:call-template name="documentation"/> // ACCESSOR function <xsl:value-of select="@name"/> ($<xsl:call-template name="argument"/>) { if (!$this->dao->validate($<xsl:call-template name="argument"/>)) return $this->dao->fault(); else return $this->dao->respond('<xsl:value-of select="@name"/>',$<xsl:call-template name="argument"/>); } </xsl:template> <!-- Converts WSDL documentation to PHP documentation --> <xsl:template name="documentation" > <xsl:param name="doc" select="wsdl:documentation" /> <xsl:if test="$doc" > <xsl:for-each select="$doc/parent::node()"> </xsl:for-each> /** <xsl:value-of select="$doc" /> */ </xsl:if> </xsl:template> <!-- Fetches name of input message --> <xsl:template name="argument" > <xsl:param name="arg" select="wsdl:input/attribute::message" /> <xsl:if test="contains($arg,':')"> <xsl:value-of select="substring-after($arg,':')"/> </xsl:if> </xsl:template> <!-- Fetches name of output message - currently unused --> <xsl:template name="return" > <xsl:param name="arg" select="wsdl:output/attribute::message" /> <xsl:if test="contains($arg,':')"> <xsl:value-of select="substring-after($arg,':')"/> </xsl:if> </xsl:template> <!-- Constucts the dispatch map --> <xsl:template name="dispatch" > $this->dispatchMap[]='<xsl:value-of select="@name" />';</xsl:template> </xsl:stylesheet>
The result...
<?php /** Example wsdl document using Uche Ogbuji's (uche@fourthought.com) example of a Snowboarding service from http://www-106.ibm.com/developerworks/webservices/library/ws-soap/code.html */ /** Abstract web service class */ class WebService { var $dao // Data access object var $dispatchMap=array(); // SOAP Dispatch map // CONSTRUCTOR function WebService (& $dao) { $this->dao=& $dao; } } class GetEndorsingBoarderPortType extends WebService { // CONSTRUCTOR function GetEndorsingBoarderPortType (& $dao) { WebService::WebService($dao); // Register dispatch map $this->dispatchMap[]='GetEndorsingBoarder'; } // ACCESSOR function GetEndorsingBoarder ($GetEndorsingBoarderRequest) { if (!$this->dao->validate($GetEndorsingBoarderRequest)) return $this->dao->fault(); else return $this->dao->respond('GetEndorsingBoarder',$GetEndorsingBoarderRequest); } } ?>
To save myself a whole ton of work, I’ve ignored the data types defined in WSDL and assumed that when registering the dispatch map, PHP‘s variable reflection will be used to identify types.
The above generated code accepts a data access object which might have the following form (using PEAR::SOAP);
<?php class DataAccessObject { var $methods=array(); // available methods var $response; var $fault; function validate ($method,$params) { if (in_array($method,$this->methods)) { $this->response=$this->{$method}($params); } else { $this->fault=new SOAP_Fault('Method does not exist','11111'); return false; } } function respond ($method,$params) { return $this->response; } function fault () { return $this->fault; } } class GetEndorsingBoarderData extends DataAccessObject { function GetEndorsingBoarderData () { $this->methods[]='getEndorsingBoarder'; } function getEndorsingBoarder ($params) { // Validate the params format if ( is_array($params) ) { foreach ($params as $param) { if (!is_string ($param) { $this->fault=new SOAP_Fault('Input structure invalid', '22222'); return false; } } $sql="SELECT * FROM boards WHERE make='".$params[0]." AND model='".$params[1]; // Perform query here $this->response=$result; return true; } else { $this->fault=new SOAP_Fault('Input structure invalid','22222'); return false; } } } ?>
Finally we have a listener;
<?php
/* Listener.php */
require_once('SOAP/Server.php');
require_once('wsdl2php_out.php');
require_once('dao.php');
$server = new SOAP_Server();
$dao=new DataAccessObject;
$boardServer = new GetEndorsingBoarderPortType($dao);
/* Right now this won't work as PEAR::SOAP needs us to specify
data types.
Options are either extend the XSL to generate the right code
for types when registering the dispatch map or we need
a SOAP server implementation that examples variable types
on the fly, as with some of the XML-RPC implementations */
$server->addObjectMap($boardserver);
$server->service($HTTP_RAW_POST_DATA);
exit();
?>
Should state again that this isn’t working code.
A few comments on the XSL Transform;
- It considers purely the abstract elements of WSDL.
- The generated code is not intended to be tied to a specific SOAP implementation.
- There’s no transformation of WSDL messages/parts/types - perhaps this would be useful but writing a stylesheet that knows what to with every XML Schema, SOAP and WSDL type is a serious effort. Also feel that it would become implementation specific by doing this. Looking at some of the XML-RPC implementations, such as the native PHP XML-RPC extension, IXR or Keith Devens XML-RPC Libary. Developers are forced to use some kind of “helper function” for binary or date types but otherwise they work nicely.
- As far as the concrete elements in a WSDL document, further XSL transforms could be used to generate the “listeners” from the elements and make sure these “bind” to the right abstract PHP class.
Anyway, may be useful for someone.
Wrox: Professional PHP Web Services
Also check out PHP UDDI ...