Source for file WSDL.php
Documentation is available at WSDL.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Shane Caraveo <Shane@Caraveo.com> Port to PEAR and more |
// | Authors: Dietrich Ayala <dietrich@ganx4.com> Original Author |
// +----------------------------------------------------------------------+
// $Id: WSDL.php,v 1.77 2005/05/26 17:37:57 yunosh Exp $
require_once 'SOAP/Base.php';
require_once 'SOAP/Fault.php';
require_once 'HTTP/Request.php';
define('WSDL_CACHE_MAX_AGE', 43200 );
define('WSDL_CACHE_USE', 0 ); // set to zero to turn off caching
* This class parses WSDL files, and can be used by SOAP::Client to
* properly register soap values for services.
* Originally based on SOAPx4 by Dietrich Ayala
* http://dietrich.ganx4.com/soapx4
* refactor namespace handling ($namespace/$ns)
* implement IDL type syntax declaration so we can generate WSDL
* @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
* @author Dietrich Ayala <dietrich@ganx4.com> Original Author
var $xsd = SOAP_XML_SCHEMA_VERSION;
* Cache max lifetime (in seconds)
* Class to use for WSDL parsing. Can be overridden for special
* cases, subclasses, etc.
* @var string $wsdlParserClass
* @param string $wsdl_uri URL to WSDL file.
* @param array $proxy Contains options for HTTP_Request class (see HTTP/Request.php).
* @param boolean $cacheUse Use WSDL caching. Defaults to false.
* @param integer $cacheMaxAge Cache max lifetime (in seconds).
* @param boolean $docs Parse documentation in the WSDL? Defaults to false.
$cacheUse = WSDL_CACHE_USE ,
$cacheMaxAge = WSDL_CACHE_MAX_AGE ,
* @deprecated use parseURL instead
function parse($wsdl_uri, $proxy = array ())
* Fill the WSDL array tree with data from a WSDL file
* @param array proxy related parameters
function parseURL($wsdl_uri, $proxy = array ())
$parser = & new $this->wsdlParserClass ($wsdl_uri, $this, $this->docs);
$this->_raiseSoapFault ($parser->fault );
* Fill the WSDL array tree with data from one or more PHP class objects
* @param mixed $wsdl_obj An object or array of objects to add to the internal WSDL tree
* @param string $service_name Name of the WSDL <service>
* @param string $service_desc Optional description of the WSDL <service>
function parseObject(&$wsdl_obj, $targetNamespace, $service_name, $service_desc = '')
$this->_raiseSoapFault ($parser->fault );
return (isset ($this->services[$this->service]['ports'][$portName]['address']['location']))
? $this->services[$this->service]['ports'][$portName]['address']['location']
: $this->_raiseSoapFault (" no endpoint for port for $portName" , $this->uri);
function _getPortName ($operation, $service)
if (isset ($this->services[$service]['ports'])) {
foreach ($this->services[$service]['ports'] as $port => $portAttrs) {
$type = $this->services[$service]['ports'][$port]['type'];
isset ($this->bindings[$portAttrs['binding']]['operations'][$operation])) {
* find the name of the first port that contains an operation of
* name $operation. always returns a the soap portName.
if (!$service) $service = $this->service;
if (isset ($this->services[$service]['ports'])) {
$portName = $this->_getPortName ($operation, $service);
if ($portName) return $portName;
// Try any service in the WSDL.
foreach ($this->services as $serviceName => $service) {
if (isset ($this->services[$serviceName]['ports'])) {
$portName = $this->_getPortName ($operation, $serviceName);
return $this->_raiseSoapFault (" no operation $operation in wsdl" , $this->uri);
&& $binding = $this->services[$this->service]['ports'][$portName]['binding']) {
// get operation data from binding
$opData = $this->bindings[$binding]['operations'][$operation];
// get operation data from porttype
$portType = $this->bindings[$binding]['type'];
return $this->_raiseSoapFault (" no port type for binding $binding in wsdl " . $this->uri);
if (isset ($this->portTypes[$portType][$operation]['parameterOrder']))
$opData['parameterOrder'] = $this->portTypes[$portType][$operation]['parameterOrder'];
$opData['output'] = array_merge($opData['output'], $this->portTypes[$portType][$operation]['output']);
return $this->_raiseSoapFault (" no operation $operation for port $portName, in wsdl" , $this->uri);
$opData['parameters'] = false;
if (isset ($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']))
$opData['namespace'] = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'];
// message data from messages
$inputMsg = $opData['input']['message'];
foreach ($this->messages[$inputMsg] as $pname => $pattrs) {
if ($opData['style'] == 'document' && $opData['input']['use'] == 'literal'
&& $pname == 'parameters') {
$opData['parameters'] = true;
$opData['namespace'] = $this->namespaces[$pattrs['namespace']];
$el = $this->elements[$pattrs['namespace']][$pattrs['type']];
if (isset ($el['elements'])) {
foreach ($el['elements'] as $elname => $elattrs) {
$opData['input']['parts'][$elname] = $elattrs;
$opData['input']['parts'][$pname] = $pattrs;
$outputMsg = $opData['output']['message'];
foreach ($this->messages[$outputMsg] as $pname => $pattrs) {
if ($opData['style'] == 'document' && $opData['output']['use'] == 'literal'
&& $pname == 'parameters') {
$el = $this->elements[$pattrs['namespace']][$pattrs['type']];
if (isset ($el['elements'])) {
foreach ($el['elements'] as $elname => $elattrs) {
$opData['output']['parts'][$elname] = $elattrs;
$opData['output']['parts'][$pname] = $pattrs;
return $this->_raiseSoapFault (" no binding for port $portName in wsdl" , $this->uri);
// Overloading lowercases function names :(
foreach ($this->services[$this->service]['ports'] as $port => $portAttrs) {
* Given a datatype, what function handles the processing?
* this is used for doc/literal requests where we receive
* a datatype, and we need to pass it to a method in out
* @param string namespace
* @returns string methodname
// see if we have an element by this name
if (isset ($this->ns[$namespace])) {
$nsp = $this->ns[$namespace];
//if (!isset($this->elements[$nsp]))
// $nsp = $this->namespaces[$nsp];
if (isset ($this->elements[$nsp][$datatype])) {
$checkmessages = array ();
// find what messages use this datatype
foreach ($this->messages as $messagename => $message) {
foreach ($message as $partname => $part) {
if ($part['type'] == $datatype) {
$checkmessages[] = $messagename;
// find the operation that uses this message
foreach($this->portTypes as $portname => $porttype) {
foreach ($porttype as $opname => $opinfo) {
foreach ($checkmessages as $messagename) {
if ($opinfo['input']['message'] == $messagename) {
if (isset ($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) &&
$soapAction = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) {
isset ($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) &&
$namespace = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) {
/* If it doesn't exist at first, flip the array and check
if (empty ($this->ns[$namespace])) {
/* If it doesn't exist now, add it. */
if (empty ($this->ns[$namespace])) {
return $this->ns[$namespace];
if (!empty ($this->ns[$namespace])) {
return $this->ns[$namespace];
$this->ns[$namespace] = $attr;
function _validateString ($string)
// XXX this should be done sooner or later
function _addArg (&$args, &$argarray, $argname)
$args .= "\$" . $argname;
if (!$this->_validateString ($argname)) {
$argarray .= " '$argname' => \$" . $argname;
function _elementArg (&$args, &$argarray, &$_argtype, $_argname)
$el = $this->elements[$_argtype['namespace']][$_argtype['type']];
$tns = isset ($this->ns[$el['namespace']]) ? $this->ns[$el['namespace']] : $_argtype['namespace'];
if (!empty ($el['complex']) || (isset ($el['type']) && isset ($this->complexTypes[$tns][$el['type']]))) {
// The element is a complex type.
$comments .= " // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n";
$attrname = "{ $_argtype['type']}_attr";
if (isset ($el['type']) && isset ($this->complexTypes[$tns][$el['type']]['attribute'])) {
$comments .= " // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n";
$comments .= " \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n";
$comments .= " \${$_argtype['type']} =& new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n";
$this->_addArg ($args, $argarray, $_argtype['type']);
if (isset ($el['type']) && isset ($this->complexTypes[$tns][$el['type']]['attribute'])) {
$args .= '$' . $attrname;
} elseif (isset ($el['elements'])) {
foreach ($el['elements'] as $ename => $element) {
$comments .= " \$$ename =& new SOAP_Value('{{$this->namespaces[$element['namespace']]}} $ename', '" .
(isset ($element['type']) ? $element['type'] : false ) . " ', \$$ename);\n";
$this->_addArg ($args, $argarray, $ename);
$comments .= " \$$_argname =& new SOAP_Value('{{$this->namespaces[$tns]}} $_argname', '{ $el['type']}', \$ $_argname);\n";
$this->_addArg ($args, $argarray, $_argname);
function _complexTypeArg (&$args, &$argarray, &$_argtype, $_argname)
if (isset ($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) {
$comments = " // $_argname is a ComplexType {$_argtype['type']},\n";
$comments .= " // refer to wsdl for more info\n";
if (isset ($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) {
$comments .= " // $_argname may require attributes, refer to wsdl for more info\n";
$wrapname = '{' . $this->namespaces[$_argtype['namespace']]. '}' . $_argtype['type'];
$comments .= " \$$_argname =& new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n";
$this->_addArg ($args, $argarray, $_argname);
* Generates stub code from the WSDL that can be saved to a file
* or eval'd into existence.
// XXX currently do not support HTTP ports
if ($port['type'] != 'soap') {
// XXX currentPort is BAD
$clienturl = $port['address']['location'];
if ($multiport || $port) {
$classname = 'WebService_' . $this->service . '_' . $port['name'];
$classname = 'WebService_' . $this->service;
$classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
if (!$this->_validateString ($classname)) {
$class = " class $classname extends SOAP_Client\n{\n" .
" function $classname(\$path = '$clienturl')\n {\n" .
" \$this->SOAP_Client(\$path, 0, 0,\n" .
foreach ($this->proxy as $key => $val) {
$class .= " '$key' => array(";
foreach ($val as $key2 => $val2) {
$class .= " '$key2' => '$val2', ";
$class .= " '$key' => '$val', ";
$class = " class $classname extends SOAP_Client\n{\n" .
" function $classname(\$path = '$clienturl')\n {\n" .
" \$this->SOAP_Client(\$path, 0);\n" .
// get the binding, from that get the port type
$primaryBinding = $port['binding']; //$this->services[$this->service]['ports'][$port['name']]["binding"];
$primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding);
$portType = $this->bindings[$primaryBinding]['type'];
$style = $this->bindings[$primaryBinding]['style'];
// XXX currentPortType is BAD
foreach ($this->portTypes[$portType] as $opname => $operation) {
$soapaction = isset ($this->bindings[$primaryBinding]['operations'][$opname]['soapAction']) ?
$this->bindings[$primaryBinding]['operations'][$opname]['soapAction'] :
if (isset ($this->bindings[$primaryBinding]['operations'][$opname]['style'])) {
$opstyle = $this->bindings[$primaryBinding]['operations'][$opname]['style'];
$use = $this->bindings[$primaryBinding]['operations'][$opname]['input']['use'];
$namespace = $this->bindings[$primaryBinding]['operations'][$opname]['input']['namespace'];
$bindingType = $this->bindings[$primaryBinding]['type'];
$ns = $this->portTypes[$bindingType][$opname]['input']['namespace'];
foreach ($operation['input'] as $argname => $argtype) {
if ($argname == 'message') {
foreach ($this->messages[$argtype] as $_argname => $_argtype) {
if ($opstyle == 'document' && $use == 'literal' &&
$_argtype['name'] == 'parameters') {
// The type or element refered to is used
$element = $_argtype['element'];
$el = $this->elements[$_argtype['namespace']][$_argtype['type']];
$namespace = $this->namespaces[$_argtype['namespace']];
// XXX need to wrap the parameters in
if (isset ($el['elements'])) {
foreach ($el['elements'] as $elname => $elattrs) {
// Is the element a complex type?
if (isset ($this->complexTypes[$elattrs['namespace']][$elname])) {
$comments .= $this->_complexTypeArg ($args, $argarray, $_argtype, $_argname);
$this->_addArg ($args, $argarray, $elname);
if ($el['complex'] && $argarray) {
$wrapname = '{' . $this->namespaces[$_argtype['namespace']]. '}' . $el['name'];
$comments .= " \${$el['name']} =& new SOAP_Value('$wrapname', false, \$v = array($argarray));\n";
$argarray = " '{$el['name']}' => \${$el['name']}";
if (isset ($_argtype['element'])) {
$comments .= $this->_elementArg ($args, $argarray, $_argtype, $_argtype['type']);
// Complex type argument.
$comments .= $this->_complexTypeArg ($args, $argarray, $_argtype, $_argname);
// Operation names are function names, so try to make sure
// it's legal. This could potentially cause collisions,
// but let's try to make everything callable and see how
// many problems that causes.
if (!$this->_validateString ($opname_php)) {
$argarray = " array($argarray)";
$class .= " function &$opname_php($args)\n {\n$comments$wrappers" .
" return \$this->call('$opname',\n" .
" array('namespace' => '$namespace',\n" .
" 'soapaction' => '$soapaction',\n" .
" 'style' => '$opstyle',\n" .
($this->trace? ",\n 'trace' => 1" : '') . "));\n" .
function &getProxy($port = '', $name = '')
if ($multiport || $port) {
$classname = 'WebService_' . $this->service . '_' . $port['name'];
$classname = 'WebService_' . $this->service;
$classname = $name . '_' . $classname;
$classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
require_once 'SOAP/Client.php';
function &_getComplexTypeForElement ($name, $namespace)
if (isset ($this->ns[$namespace]) &&
isset ($this->elements[$this->ns[$namespace]][$name]['type'])) {
$type = $this->elements[$this->ns[$namespace]][$name]['type'];
$ns = $this->elements[$this->ns[$namespace]][$name]['namespace'];
$t = $this->_getComplexTypeForElement ($name, $namespace);
// is the type an element?
$t = $this->_getComplexTypeForElement ($name, $ns);
// no, get it from complex types directly
if (isset ($t['elements'][$child_name]['type']))
return $t['elements'][$child_name]['type'];
// see if it's a complex type so we can deal properly with
// look up the name in the wsdl and validate the type.
list ($arraytype_ns, $arraytype, $array_depth) = isset ($types[$type]['arrayType'])?
$this->_getDeepestArrayType ($types[$type]['namespace'], $types[$type]['arrayType'])
: array ($this->namespaces[$types[$type]['namespace']], null , 0 );
return array ($types[$type]['type'], $arraytype, $arraytype_ns, $array_depth);
list ($arraytype_ns, $arraytype, $array_depth) =
$this->_getDeepestArrayType ($types[$type]['namespace'], $types[$type]['arrayType']);
return array ('Array', $arraytype, $arraytype_ns, $array_depth);
$type = $types[$type]['elements']['type'];
return array ($type, null , $this->namespaces[$types[$type]['namespace']], null );
if ($type && $type_namespace) {
// this code currently handles only one way of encoding array types in wsdl
// need to do a generalized function to figure out complex types
$p = $this->ns[$type_namespace];
if ($arrayType = $this->complexTypes[$p][$type]['arrayType']) {
} elseif ($this->complexTypes[$p][$type]['order']== 'sequence' &&
$arrayType = $arg['type'];
foreach ($this->complexTypes[$p][$type]['elements'] as $element) {
if ($element['name'] == $type) {
$arrayType = $element['type'];
$type = $element['type'];
return array ($type, $arrayType, $type_namespace, null );
return array (null , null , null , null );
* Recurse through the WSDL structure looking for the innermost
* array type of multi-dimensional arrays.
* Takes a namespace prefix and a type, which can be in the form
* 'type' or 'type[]', and returns the full namespace URI, the
* type of the most deeply nested array type found, and the number
* @return mixed array or nothing
function _getDeepestArrayType ($nsPrefix, $arrayType)
$arrayType = ereg_replace ('\[\]$', '', $arrayType);
// Protect against circular references
// XXX We really need to remove trail from this altogether (it's very inefficient and
// in the wrong place!) and put circular reference checking in when the WSDL info
// is generated in the first place
return array (null , null , - count($trail));
$trail[] = $nsPrefix . ':' . $arrayType;
$result = $this->_getDeepestArrayType ($this->complexTypes[$nsPrefix][$arrayType]['namespace'],
return array ($result[0 ], $result[1 ], $result[2 ] + 1 );
return array ($this->namespaces[$nsPrefix], $arrayType, 0 );
* Cache max lifetime (in seconds)
var $_cacheMaxAge = null;
* SOAP_WSDL_Cache constructor
* @param boolean use caching
* @param int cache max lifetime (in seconds)
$cacheMaxAge = WSDL_CACHE_MAX_AGE )
parent ::SOAP_Base ('WSDLCACHE');
$this->_cacheUse = $cacheUse;
$this->_cacheMaxAge = $cacheMaxAge;
* return the path to the cache, if it doesn't exist, make it
if (!$dir) $dir = " ./wsdlcache";
* Retrieves a file from cache if it exists, otherwise retreive from net,
* add to cache, and return from cache.
* @param string URL to WSDL
* @param array proxy parameters
* @param int expected MD5 of WSDL URL
function get($wsdl_fname, $proxy_params = array (), $cache = 0 )
$cachename = $md5_wsdl = $file_data = '';
// Try to retrieve WSDL from cache
$wf = fopen($cachename, 'rb');
$md5_wsdl = md5($file_data);
if ($cache != $md5_wsdl) {
return $this->_raiseSoapFault ('WSDL Checksum error!', $wsdl_fname);
//print cache_mtime, time()
if ($cache_mtime + $this->_cacheMaxAge < time()) {
$md5_wsdl = ''; // refetch
// Not cached or not using cache. Retrieve WSDL from URL
// this section should be replace by curl at some point
if (!preg_match('/^(https?|file):\/\//', $wsdl_fname)) {
return $this->_raiseSoapFault (" Unable to read local WSDL $wsdl_fname" , $wsdl_fname);
$rq = & new HTTP_Request ($uri[0 ], $proxy_params);
// the user agent HTTP_Request uses fouls things up
$rq->addRawQueryString ($uri[1 ]);
if (isset ($proxy_params['proxy_host']) &&
isset ($proxy_params['proxy_port']) &&
isset ($proxy_params['proxy_user']) &&
isset ($proxy_params['proxy_pass'])) {
$rq->setProxy ($proxy_params['proxy_host'], $proxy_params['proxy_port'],
$proxy_params['proxy_user'], $proxy_params['proxy_pass']);
} elseif (isset ($proxy_params['proxy_host']) &&
isset ($proxy_params['proxy_port'])) {
$rq->setProxy ($proxy_params['proxy_host'], $proxy_params['proxy_port']);
$result = $rq->sendRequest ();
if (PEAR ::isError ($result)) {
return $this->_raiseSoapFault (" Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode (), $wsdl_fname);
$file_data = $rq->getResponseBody ();
return $this->_raiseSoapFault (" Unable to retrieve WSDL $wsdl_fname, no http body" , $wsdl_fname);
$md5_wsdl = md5($file_data);
$fp = fopen($cachename, "wb");
if ($this->_cacheUse && $cache && $cache != $md5_wsdl) {
return $this->_raiseSoapFault ("WSDL Checksum error!", $wsdl_fname);
* Define internal arrays of bindings, ports, operations,
parent ::SOAP_Base ('WSDLPARSER');
// Check whether content has been read.
$fd = $this->cache->get ($uri, $this->wsdl->proxy );
if (PEAR ::isError ($fd)) {
return $this->_raiseSoapFault ($fd);
$detail = sprintf('XML error on line %d: %s',
return $this->_raiseSoapFault (" Unable to parse WSDL file $uri\n$detail" );
$qname = & new QName($name);
if ($ns && ((!$this->tns && strcasecmp($qname->name , 'definitions') == 0 ) || $ns == $this->tns)) {
$this->currentTag = $qname->name;
// Find status, register data.
// No parent should be in the stack.
if (!$parent_tag || $parent_tag == 'types') {
$this->schema = $this->wsdl->getNamespaceAttributeName ($attrs['targetNamespace']);
$this->schema = $this->wsdl->getNamespaceAttributeName ($this->wsdl->tns );
$this->wsdl->complexTypes [$this->schema] = array ();
if ($parent_tag == 'schema') {
if (!isset ($attrs['namespace'])) {
$attrs['namespace'] = $this->schema;
$qn = & new QName($attrs['base']);
if (isset ($attrs['type'])) {
$qn = & new QName($attrs['type']);
$attrs['type'] = $qn->name;
$attrs['namespace'] = $qn->ns;
if (isset ($attrs['ref'])) {
$qn = & new QName($attrs['ref']);
if (!isset ($attrs['namespace'])) {
$attrs['namespace'] = $this->schema;
if ($parent_tag == 'schema') {
// we're inside a complexType
if (!empty ($attrs['base'])) {
$qn = & new QName($attrs['base']);
// Types that extend from other types aren't
// *of* those types. Reflect this by denoting
// which type they extend. I'm leaving the
// 'type' setting here since I'm not sure what
// removing it might break at the moment.
if ($qname->name == 'extension') {
if (isset ($attrs['name'])) {
if (isset ($attrs['ref'])) {
$q = & new QName($attrs['ref']);
foreach ($attrs as $k => $v) {
if ($k != 'ref' && strstr($k, $q->name )) {
if ($q->name == 'arrayType') {
// sect 2.3 wsdl:message child wsdl:part
if (isset ($attrs['type'])) {
$qn = & new QName($attrs['type']);
} elseif (isset ($attrs['element'])) {
$qn = & new QName($attrs['element']);
$attrs['type'] = $qn->name;
$attrs['namespace'] = $qn->ns;
// children: wsdl:input wsdl:output wsdl:fault
// wsdl:input wsdl:output wsdl:fault
// attributes: name message parameterOrder(optional)
$qn = & new QName($attrs['message']);
// this deals with wsdl section 3 soap binding
// soap:binding, attributes: transport(required), style(optional, default = document)
// if style is missing, it is assumed to be 'document'
if (!isset ($attrs['style'])) {
$attrs['style'] = 'document';
// soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style)
if (!isset ($attrs['style'])) {
// part - optional. listed parts must appear in body, missing means all parts appear in body
// use - required. encoded|literal
// encodingStyle - optional. space seperated list of encodings (uri's)
// soap:fault attributes: name use encodingStyle namespace
// soap:header attributes: message part use encodingStyle namespace
// soap:header attributes: message part use encodingStyle namespace
['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs;
// error! not a valid element inside binding
// XXX verify correct namespace
// for now, default is the 'wsdl' namespace
// other possible namespaces include smtp, http, etc. for alternate bindings
// wsdl:operation attributes: name
// wsdl:input attributes: name
$this->opStatus = $qname->name;
// http:binding attributes: verb
// http:operation attributes: location
// parent: wsdl:operation
// http:urlEncoded attributes: location
// parent: wsdl:input wsdl:output etc.
// http:urlReplacement attributes: location
// parent: wsdl:input wsdl:output etc.
// all mime parts are children of wsdl:input, wsdl:output, etc.
// <mime:content part="nmtoken"? type="string"?/>
// part attribute only required if content is child of multipart related,
// it contains the name of the part
// type attribute contains the mime type
// sect 5.4 mime:multipartRelated
// <mime:mimeXml part="nmtoken"?/>
// http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
// all DIME parts are children of wsdl:input, wsdl:output, etc.
// appears in binding section
// sect 2.6 wsdl:port attributes: name binding
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort] = $attrs;
// XXX hack to deal with binding namespaces
$qn = & new QName($attrs['binding']);
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name;
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->ns;
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['address'] = $attrs;
// what TYPE of port is it? SOAP or HTTP?
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['type']= 'http';
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['type']= 'soap';
// Shouldn't happen, we'll assume SOAP.
$this->wsdl->services [$this->currentService]['ports'][$this->currentPort]['type']= 'soap';
// Top level elements found under wsdl:definitions.
// sect 2.1.1 wsdl:import attributes: namespace location
if ((isset ($attrs['location']) || isset ($attrs['schemaLocation'])) &&
!isset ($this->wsdl->imports [$attrs['namespace']])) {
$uri = isset ($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation'];
if (!isset ($location['scheme'])) {
$uri = $this->mergeUrl ($base, $uri);
$this->wsdl->imports [$attrs['namespace']] = $attrs;
$import_parser = & new $import_parser_class($uri, $this->wsdl, $this->docs);
if ($import_parser->fault ) {
unset ($this->wsdl->imports [$attrs['namespace']]);
$this->currentImport = $attrs['namespace'];
// Continue on to the 'types' case - lack of break; is
// We can hit this at the top level if we've been asked to
if (!empty ($attrs['targetNamespace'])) {
$this->schema = $this->wsdl->getNamespaceAttributeName ($attrs['targetNamespace']);
$this->schema = $this->wsdl->getNamespaceAttributeName ($this->wsdl->tns );
$this->wsdl->complexTypes [$this->schema] = array ();
// sect 2.3 wsdl:message attributes: name children:wsdl:part
if (isset ($attrs['name'])) {
// sect 2.4 wsdl:portType
// children: wsdl:operation
// sect 2.5 wsdl:binding attributes: name type
// children: wsdl:operation soap:binding http:binding
if ($qname->ns && $qname->ns != $this->tns) {
$qn = & new QName($attrs['type']);
// sect 2.7 wsdl:service attributes: name children: ports
$this->currentService = $attrs['name'];
$this->wsdl->services [$this->currentService]['ports'] = array ();
// sec 2.1 wsdl:definitions
// attributes: name targetNamespace xmlns:*
// children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service
$this->wsdl->definition = $attrs;
foreach ($attrs as $key => $value) {
if (strstr($key, 'xmlns:') !== false ) {
// XXX need to refactor ns handling.
$this->wsdl->namespaces [$qn->name ] = $value;
$this->wsdl->ns [$value] = $qn->name;
if ($key == 'targetNamespace' ||
if (in_array($value, $this->_XMLSchema)) {
$this->wsdl->xsd = $value;
$namespace = 'xmlns:' . $ns;
if (!$this->wsdl->definition [$namespace]) {
return $this->_raiseSoapFault (" parse error, no namespace for $namespace" , $this->uri);
/* Correct the type for sequences with multiple
if (stristr($name, 'complexType')) {
} elseif (stristr($name, 'element')) {
* Element content handler.
// Store the documentation in the WSDL file.
if ($this->currentTag == 'documentation') {
$ptr = & $this->wsdl->services [$this->currentService];
if (!isset ($ptr['documentation'])) {
$ptr['documentation'] = '';
$ptr['documentation'] .= ' ';
$ptr['documentation'] .= $data;
* $parsed is an array returned by parse_url().
function mergeUrl ($parsed, $path)
if (!empty ($parsed['scheme'])) {
$sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://');
$uri = $parsed['scheme'] . $sep;
if (isset ($parsed['pass'])) {
$uri .= " $parsed[user]:$parsed[pass]@";
} elseif (isset ($parsed['user'])) {
$uri .= " $parsed[user]@";
if (isset ($parsed['host'])) {
if (isset ($parsed['port'])) {
$uri .= " :$parsed[port]";
if ($path[0 ] != '/' && isset ($parsed['path'])) {
if ($parsed['path'][strlen($parsed['path']) - 1 ] != '/') {
$path = dirname($parsed['path']) . '/' . $path;
$path = $parsed['path'] . $path;
$path = $this->_normalize ($path);
$sep = $path[0 ] == '/' ? '' : '/';
function _normalize ($path_str)
$strArr = preg_split('/(\/)/', $path_str, -1 , PREG_SPLIT_NO_EMPTY );
for ($i = 0; $i < count($strArr); $i++ ) {
if ($strArr[$i] != ' ..') {
if ($strArr[$i] != ' .') {
$pwdArr[$j] = $strArr[$i];
$pwd = (strlen($pStr) > 0 ) ? ('/' . $pStr) : '/';
* Parses the types and methods used in web service objects into the internal
* data structures used by SOAP_WSDL.
* Assumes the SOAP_WSDL class is unpopulated to start with.
* @author Chris Coe <info@intelligentstreaming.com>
* Target namespace for the WSDL document will have the following
* Reference to the SOAP_WSDL object to populate.
* @param $objects Reference to the object or array of objects to parse
* @param $wsdl Reference to the SOAP_WSDL object to populate
* @param $targetNamespace The target namespace of schema types etc.
* @param $service_name Name of the WSDL <service>
* @param $service_desc Optional description of the WSDL <service>
parent ::SOAP_Base ('WSDLOBJECTPARSER');
// Set up the SOAP_WSDL object
$this->_initialise ($service_name);
// Parse each web service object
$wsdl_ref = (is_array($objects)? $objects : array (&$objects));
foreach ($wsdl_ref as $ref_item) {
return $this->_raiseSoapFault ('Invalid web service object passed to object parser', 'urn:' . get_class($object));
if ($this->_parse ($ref_item, $targetNamespace, $service_name) != true )
// Build bindings from abstract data.
if ($this->fault == null ) {
$this->_generateBindingsAndServices ($targetNamespace, $service_name, $service_desc);
* Initialise the SOAP_WSDL tree (destructive).
* If the object has already been initialised, the only effect
* will be to change the tns namespace to the new service name.
* @param $service_name Name of the WSDL <service>
function _initialise ($service_name)
// Set up the basic namespaces that all WSDL definitions use.
$this->wsdl->namespaces [$this->tnsPrefix] = 'urn:' . $service_name; // Target namespace
$this->wsdl->namespaces ['xsd'] = array_search('xsd', $this->_namespaces); // XML Schema
$this->wsdl->namespaces ['SOAP-ENC'] = array_search('SOAP-ENC', $this->_namespaces); // SOAP types
// XXX Refactor $namespace/$ns for Shane :-)
unset ($this->wsdl->ns ['urn:' . $service_name]);
// Imports are not implemented in WSDL generation from classes.
* Parser - takes a single object to add to tree
* @param $object Reference to the object to parse
* @param $service_name Name of the WSDL <service>
function _parse (&$object, $schemaNamespace, $service_name)
// Create namespace prefix for the schema
// XXX not very elegant :-(
list ($schPrefix, $foo) = $this->_getTypeNs ('{' . $schemaNamespace. '}');
// Parse all the types defined by the object in whatever
// schema language we are using (currently __typedef arrays)
foreach ($object->__typedef as $typeName => $typeValue) {
// Get/create namespace definition
list ($nsPrefix, $typeName) = $this->_getTypeNs ($typeName);
// Create type definition
$this->wsdl->complexTypes [$schPrefix][$typeName] = array ('name' => $typeName);
$thisType = & $this->wsdl->complexTypes [$schPrefix][$typeName];
// According to Dmitri's documentation, __typedef comes in two
// Array = array(array("item" => "value"))
// Struct = array("item1" => "value1", "item2" => "value2", ...)
$thisType['type'] = 'Array';
list ($nsPrefix, $typeName) = $this->_getTypeNs (current(current($typeValue)));
$thisType['namespace'] = $nsPrefix;
$thisType['arrayType'] = $typeName . '[]';
$thisType['type'] = 'Struct';
$thisType['order'] = 'all';
$thisType['namespace'] = $nsPrefix;
$thisType['elements'] = array ();
foreach ($typeValue as $elementName => $elementType) {
list ($nsPrefix, $typeName) = $this->_getTypeNs ($elementType);
$thisType['elements'][$elementName]['name'] = $elementName;
$thisType['elements'][$elementName]['type'] = $typeName;
$thisType['elements'][$elementName]['namespace'] = $nsPrefix;
return $this->_raiseSoapFault (" The type definition for $nsPrefix:$typeName is invalid." , 'urn:' . get_class($object));
return $this->_raiseSoapFault (" The type definition for $nsPrefix:$typeName is invalid." , 'urn:' . get_class($object));
// Create an empty element array with the target namespace
// prefix, to match the results of WSDL parsing.
$this->wsdl->elements [$schPrefix] = array ();
// Populate tree with message information
// *** <wsdl:message> ***
foreach ($object->__dispatch_map as $operationName => $messages) {
foreach ($messages as $messageType => $messageParts) {
$this->wsdl->messages [$operationName . 'Request'] = array ();
$thisMessage = & $this->wsdl->messages [$operationName . 'Request'];
$this->wsdl->messages [$operationName . 'Response'] = array ();
$thisMessage = & $this->wsdl->messages [$operationName . 'Response'];
if (isset ($thisMessage)) {
foreach ($messageParts as $partName => $partType) {
list ($nsPrefix, $typeName) = $this->_getTypeNs ($partType);
$thisMessage[$partName] = array (
// Populate tree with portType information
// XXX Current implementation only supports one portType that
// encompasses all of the operations available.
// *** <wsdl:portType> ***
if (!isset ($this->wsdl->portTypes [$service_name . 'Port'])) {
$this->wsdl->portTypes [$service_name . 'Port'] = array ();
$thisPortType = & $this->wsdl->portTypes [$service_name . 'Port'];
foreach ($object->__dispatch_map as $operationName => $messages) {
$thisPortType[$operationName] = array ('name' => $operationName);
foreach ($messages as $messageType => $messageParts) {
$thisPortType[$operationName]['input'] = array (
'message' => $operationName . 'Request',
$thisPortType[$operationName]['output'] = array (
'message' => $operationName . 'Response',
* Take all the abstract WSDL data and build concrete bindings and
* services (destructive).
* XXX Current implementation discards $service_desc.
* @param $schemaNamespace Namespace for types etc.
* @param $service_name Name of the WSDL <service>
* @param $service_desc Optional description of the WSDL <service>
function _generateBindingsAndServices ($schemaNamespace, $service_name, $service_desc = '')
// Populate tree with bindings information
// XXX Current implementation only supports one binding that
// matches the single portType and all of its operations.
// XXX Is this the correct use of $schemaNamespace here?
// *** <wsdl:binding> ***
$this->wsdl->bindings [$service_name . 'Binding'] = array (
'type' => $service_name . 'Port',
'operations' => array ());
$thisBinding = & $this->wsdl->bindings [$service_name . 'Binding'];
foreach ($this->wsdl->portTypes [$service_name . 'Port'] as $operationName => $operationData) {
$thisBinding['operations'][$operationName] = array (
'soapAction' => $schemaNamespace . '#' . $operationName,
'style' => $thisBinding['style']);
foreach (array ('input', 'output') as $messageType)
if (isset ($operationData[$messageType])) {
$thisBinding['operations'][$operationName][$messageType] = array (
'namespace' => $schemaNamespace,
// Populate tree with service information
// XXX Current implementation supports one service which groups
// all of the ports together, one port per binding
// *** <wsdl:service> ***
$this->wsdl->services [$service_name . 'Service'] = array ('ports' => array ());
$thisService = & $this->wsdl->services [$service_name . 'Service']['ports'];
foreach ($this->wsdl->bindings as $bindingName => $bindingData) {
$thisService[$bindingData['type']] = array (
'name' => $bindingData['type'],
'binding' => $bindingName,
'address' => array ('location' =>
'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] .
(isset ($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')),
$this->wsdl->set_service ($service_name . 'Service');
// Create WSDL definition
// *** <wsdl:definitions> ***
$this->wsdl->definition = array (
foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) {
$this->wsdl->definition ['xmlns:' . $nsPrefix] = $namespace;
* This function is adapted from Dmitri V's implementation of
* DISCO/WSDL generation. It separates namespace from type name in
* a __typedef key and creates a new namespace entry in the WSDL
* structure if the namespace has not been used before. The
* namespace prefix and type name are returned. If no namespace is
* specified, xsd is assumed.
* We will not need this function anymore once __typedef is
function _getTypeNs ($type)
if (isset ($m[1 ][0 ]) && $m[1 ][0 ] != '') {
$ns_pref = 'ns' . count($this->wsdl->namespaces );
$this->wsdl->ns [$m[1 ][0 ]] = $ns_pref;
$this->wsdl->namespaces [$ns_pref] = $m[1 ][0 ];
$typens = $this->wsdl->ns [$m[1 ][0 ]];
return array ($typens, $type);
Documentation generated on Mon, 11 Mar 2019 14:20:06 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|