Skip to content

Commit

Permalink
enh yiisoft#94, added support for document/literal WSDL in generator,…
Browse files Browse the repository at this point in the history
… inspired by Zend 2.0 Soap Autodiscover class
  • Loading branch information
nineinchnick committed Feb 21, 2013
1 parent 2370a9e commit a61b6de
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Version 1.1.14 work in progress
- Bug #2078: Fixed problem with "undefined" parameter in query string when using CListView or CGridView with enableHistory (Parpaing)
- Bug #2086: Fixed .hgignore rule for assets folder (GeXu3, Koduc)
- Bug #2112: Fixed broken yiic shell CRUD command (mbischof)
- Enh #94: Web services: Implement document/literal encoding for WDSL (nineinchnick)
- Enh: Better CFileLogRoute performance (Qiang, samdark)
- Enh #1847: Added COutputCache::varyByLanguage to generate separate cache for different languages (Obramko)
- Enh #1948: Tidy up and improve html5 input support in CHtml and CActiveForm (phpnode)
Expand Down
160 changes: 133 additions & 27 deletions framework/web/services/CWsdlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ class CWsdlGenerator extends CComponent
* If not set, it defaults to "urn:{$className}wsdl".
*/
public $serviceName;
/**
* @var array
* soap:body operation style options
*/
public $operationBodyStyle = array('use' => 'encoded', 'encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/");
/**
* @var array
* soap:operation style
*/
public $bindingStyle = 'rpc';
/**
* @var string
* soap:operation transport
*/
public $bindingTransport = 'http://schemas.xmlsoap.org/soap/http';

protected static $typeMap=array(
'string'=>'xsd:string',
Expand All @@ -121,6 +136,7 @@ class CWsdlGenerator extends CComponent

protected $operations;
protected $types;
protected $elements;
protected $messages;

/**
Expand All @@ -134,6 +150,7 @@ public function generateWsdl($className, $serviceUrl, $encoding='UTF-8')
{
$this->operations=array();
$this->types=array();
$this->elements=array();
$this->messages=array();
if($this->serviceName===null)
$this->serviceName=$className;
Expand Down Expand Up @@ -167,17 +184,55 @@ protected function processMethod($method)
$n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
if($n>count($params))
$n=count($params);
for($i=0;$i<$n;++$i)
$message[$params[$i]->getName()]=array($this->processType($matches[1][$i]), trim($matches[3][$i])); // name => type, doc
if ($this->bindingStyle == 'rpc')
{
for($i=0;$i<$n;++$i)
$message[$params[$i]->getName()]=array(
'type'=>$this->processType($matches[1][$i]),
'doc'=>trim($matches[3][$i]),
);
}
else
{
$this->elements[$methodName] = array();
for($i=0;$i<$n;++$i)
$this->elements[$methodName][$params[$i]->getName()]=array(
'type'=>$this->processType($matches[1][$i]),
'nillable'=>$params[$i]->isOptional(),
);
$message['parameters'] = array('element'=>'tns:'.$methodName);
}

$this->messages[$methodName.'Request']=$message;
$this->messages[$methodName.'In']=$message;

if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
$return=array($this->processType($matches[1]),trim($matches[2])); // type, doc
else
$return=null;
$this->messages[$methodName.'Response']=array('return'=>$return);

if ($this->bindingStyle == 'rpc')
{
if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
$return=array(
'type'=>$this->processType($matches[1]),
'doc'=>trim($matches[2]),
);
else
$return=null;
$this->messages[$methodName.'Out']=array('return'=>$return);
}
else
{
if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
{
$this->elements[$methodName.'Response'][$methodName.'Result']=array(
'type'=>$this->processType($matches[1]),
);
}
$this->messages[$methodName.'Out']=array('parameters'=>array('element'=>'tns:'.$methodName.'Response'));
}

if(preg_match('/^\/\*+\s*([^@]*?)\n@/s',$comment,$matches))
$doc=trim($matches[1]);
else
Expand Down Expand Up @@ -279,7 +334,7 @@ protected function buildDOM($serviceUrl,$encoding)
*/
protected function addTypes($dom)
{
if($this->types===array())
if($this->types===array() && $this->elements===array())
return;
$types=$dom->createElement('wsdl:types');
$schema=$dom->createElement('xsd:schema');
Expand All @@ -295,21 +350,33 @@ protected function addTypes($dom)
$complexType->setAttribute('name',substr($xmlType,4));
else
$complexType->setAttribute('name',$xmlType);
$complexContent=$dom->createElement('xsd:complexContent');
$restriction=$dom->createElement('xsd:restriction');
$restriction->setAttribute('base','soap-enc:Array');
$attribute=$dom->createElement('xsd:attribute');
$attribute->setAttribute('ref','soap-enc:arrayType');
$attribute->setAttribute('wsdl:arrayType',substr($xmlType,0,strlen($xmlType)-5).'[]');

$arrayType = ($dppos=strpos($xmlType,':')) !==false ? substr($xmlType,$dppos + 1) : $xmlType; // strip namespace, if any
$arrayType = substr($arrayType,0,-5); // strip 'Array' from name
$arrayType = (isset(self::$typeMap[$arrayType]) ? 'xsd:' : 'tns:') .$arrayType.'[]';
$attribute->setAttribute('wsdl:arrayType',$arrayType);

$restriction->appendChild($attribute);
$complexContent->appendChild($restriction);
$complexType->appendChild($complexContent);
if ($this->operationBodyStyle['use'] == 'encoded')
{
$complexContent=$dom->createElement('xsd:complexContent');
$restriction=$dom->createElement('xsd:restriction');
$restriction->setAttribute('base','soap-enc:Array');
$attribute=$dom->createElement('xsd:attribute');
$attribute->setAttribute('ref','soap-enc:arrayType');
$attribute->setAttribute('arrayType',(isset(self::$typeMap[$arrayType]) ? 'xsd:' : 'tns:') .$arrayType.'[]');

$restriction->appendChild($attribute);
$complexContent->appendChild($restriction);
$complexType->appendChild($complexContent);
}
else
{
$sequence=$dom->createElement('xsd:sequence');
$element=$dom->createElement('xsd:element');
$element->setAttribute('name','item');
$element->setAttribute('type',(isset(self::$typeMap[$arrayType]) ? self::$typeMap[$arrayType] : 'tns:'.$arrayType));
$element->setAttribute('minOccurs','0');
$element->setAttribute('maxOccurs','unbounded');
$sequence->appendChild($element);
$complexType->appendChild($sequence);
}
}
elseif(is_array($xmlType))
{
Expand All @@ -331,9 +398,32 @@ protected function addTypes($dom)
$complexType->appendChild($all);
}
$schema->appendChild($complexType);
$types->appendChild($schema);
}

foreach($this->elements as $name=>$parameters)
{
$element=$dom->createElement('xsd:element');
$element->setAttribute('name',$name);
$complexType=$dom->createElement('xsd:complexType');
if (!empty($parameters))
{
$sequence=$dom->createElement('xsd:sequence');
foreach($parameters as $paramName=>$paramOpts)
{
$innerElement=$dom->createElement('xsd:element');
$innerElement->setAttribute('name',$paramName);
$innerElement->setAttribute('type',$paramOpts['type']);
if (isset($paramOpts['nillable']) && $paramOpts['nillable'])
{
$innerElement->setAttribute('nillable','true');
}
$sequence->appendChild($innerElement);
}
$complexType->appendChild($sequence);
}
$element->appendChild($complexType);
$schema->appendChild($element);
}
$types->appendChild($schema);
$dom->documentElement->appendChild($types);
}

Expand All @@ -352,7 +442,14 @@ protected function addMessages($dom)
{
$partElement=$dom->createElement('wsdl:part');
$partElement->setAttribute('name',$partName);
$partElement->setAttribute('type',$part[0]);
if (isset($part['type']))
{
$partElement->setAttribute('type',$part['type']);
}
if (isset($part['element']))
{
$partElement->setAttribute('element',$part['element']);
}
$element->appendChild($partElement);
}
}
Expand Down Expand Up @@ -383,9 +480,9 @@ protected function createPortElement($dom,$name,$doc)
$operation->setAttribute('name',$name);

$input = $dom->createElement('wsdl:input');
$input->setAttribute('message', 'tns:'.$name.'Request');
$input->setAttribute('message', 'tns:'.$name.'In');
$output = $dom->createElement('wsdl:output');
$output->setAttribute('message', 'tns:'.$name.'Response');
$output->setAttribute('message', 'tns:'.$name.'Out');

$operation->appendChild($dom->createElement('wsdl:documentation',$doc));
$operation->appendChild($input);
Expand All @@ -404,8 +501,8 @@ protected function addBindings($dom)
$binding->setAttribute('type','tns:'.$this->serviceName.'PortType');

$soapBinding=$dom->createElement('soap:binding');
$soapBinding->setAttribute('style','rpc');
$soapBinding->setAttribute('transport','http://schemas.xmlsoap.org/soap/http');
$soapBinding->setAttribute('style',$this->bindingStyle);
$soapBinding->setAttribute('transport',$this->bindingTransport);
$binding->appendChild($soapBinding);

$dom->documentElement->appendChild($binding);
Expand All @@ -424,15 +521,24 @@ protected function createOperationElement($dom,$name)
$operation->setAttribute('name', $name);
$soapOperation = $dom->createElement('soap:operation');
$soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name);
$soapOperation->setAttribute('style','rpc');
if ($this->bindingStyle == 'rpc')
{
$soapOperation->setAttribute('style','rpc');
}

$input = $dom->createElement('wsdl:input');
$output = $dom->createElement('wsdl:output');

$soapBody = $dom->createElement('soap:body');
$soapBody->setAttribute('use', 'encoded');
$soapBody->setAttribute('namespace', $this->namespace);
$soapBody->setAttribute('encodingStyle', 'http://schemas.xmlsoap.org/soap/encoding/');
$operationBodyStyle = $this->operationBodyStyle;
if ($this->bindingStyle == 'rpc' && !isset($operationBodyStyle['namespace']))
{
$operationBodyStyle['namespace'] = $this->namespace;
}
foreach($operationBodyStyle as $attributeName=>$attributeValue)
{
$soapBody->setAttribute($attributeName, $attributeValue);
}
$input->appendChild($soapBody);
$output->appendChild(clone $soapBody);

Expand Down

7 comments on commit a61b6de

@marcovtwout
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the following PHP defines instead of doing string comparisons:

SOAP_RPC
SOAP_DOCUMENT

SOAP_ENCODED
SOAP_LITERAL

@nineinchnick
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, because I put bindingStyle and operationBodyStyle directly in the WSDL and bindingStyle needs to be a string of either 'rpc' or 'document' and operationBodyStyle's 'use' key neends to be either 'encoded' or 'literal' .

I've tried to search over php docs to see the usage of those constants but couldn't find none.

@marcovtwout
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These core PHP constants are defined here: http://php.net/manual/en/soap.constants.php

Maybe it's good to define and use your own consts in this file, for example:

STYLE_RPC = 'rpc'
STYLE_DOCUMENT = 'document'
USE_ENCODED = 'encoded'
USE_LITERAL = 'literal'

@nineinchnick
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, and they are defined as integers. I really don't think that using a constant instead of a string is much different. There are only two variants. There won't be more, there are only legacy SOAP apps left and everybody is doing the new stuff as RESTful. Besides, if we would use document/literal as the defaults probably no one would wan't to change it back to rpc/encoded.

Anyway, I just hope this gets merged as it is.

@marcovtwout
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you are using (lots of) comparisons such as "if ($bindingStyle == 'rpc'), in my opinoin it is much cleaner to use "if ($bindingStyle == self::STYLE_RPC)"

It doesn't really matter if there are two options or ten, using these consts softly restricts the possible values, and it is less error prone.

@nineinchnick
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, thanks for convincing me. Now I don't even know why I was opposing this. Fixed in commit 3c94a7a.

@marcovtwout
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Details matter :) Thanks for updating!

Please sign in to comment.