CWsdlGenerator
| Пакет | system.web.services |
|---|---|
| Наследование | класс CWsdlGenerator » CComponent |
| С момента | 1.0 |
| Исходный код | framework/web/services/CWsdlGenerator.php |
Генерация WSDL основана на комментариях в файле класса сервиса. В частности, он распознаёт тег «@soap» в комментариях и извлекает определения методов и типов API.
В классе сервиса удалённо вызываемый метод должен быть публичным методом с блоком комментариев, содержащим тег «@soap». В комментарии необходимо указать тип и имя каждого входного параметра и тип возвращаемого значения в формате phpdoc.
CWsdlGenerator распознаёт следующие примитивные типы (регистрозависимые) в объявлениях параметров и возвращаемого значения:
- str/string: сопоставляется с xsd:string;
- int/integer: сопоставляется с xsd:int;
- float/double: сопоставляется с xsd:float;
- bool/boolean: сопоставляется с xsd:boolean;
- date: сопоставляется с xsd:date;
- time: сопоставляется с xsd:time;
- datetime: сопоставляется с xsd:dateTime;
- array: сопоставляется с xsd:string;
- object: сопоставляется с xsd:struct;
- mixed: сопоставляется с xsd:anyType.
Если тип не является примитивным типом, он рассматривается как тип класса, и CWsdlGenerator будет искать его объявления свойств. Учитываются только публичные свойства, и каждое из них должно быть связано с блоком комментариев, содержащим тег «@soap». В блоке комментариев необходимо указать тип свойства.
CWsdlGenerator распознаёт тип массива в следующем формате:
typeName[]: maps to tns:typeNameArray
Пример объявления удалённо вызываемого метода:
/ **
* A foo method.
* @param string name of something
* @param string value of something
* @return string[] some array
* @soap
* /
public function foo($name,$value) {...}
Пример объявления класса с удалённо доступными свойствами:
class Foo {
/ **
* @var string name of foo {nillable=1, minOccurs=0, maxOccurs=2}
* @soap
* /
public $name;
/ **
* @var Member[] members of foo
* @soap
* /
public $members;
}
В примере свойство 'members' представляет собой массив объектов 'Member'. Поскольку 'Member' не является примитивным типом, CWsdlGenerator продолжит поиск определения 'Member'. Допускается определение дополнительных атрибутов (nillable, minOccurs, maxOccurs) для каждого свойства, заключив определения в фигурные скобки и разделив их запятыми:
{[attribute1 = value1][, attribute2 = value2], ...}
где атрибут может быть одним из следующих:
- nillable = [0|1|true|false]
- minOccurs = n; где n>=0
- maxOccurs = n; где [n>=0|unbounded]
Кроме того, каждому сложному типу данных можно назначить флаг индикатора soap, объявляющий специальное использование для такого типа данных. Индикатор soap должен быть объявлен в блоке комментариев с тегом «@soap-indicator». В настоящее время поддерживаются следующие индикаторы soap:
- all - (по умолчанию) позволяет любой порядок сортировки дочерних узлов
- sequence - все дочерние узлы в файле WSDL XML будут ожидаться в заданном порядке
- choice - предоставленное значение может быть любым из дочерних элементов
В следующем примере класс Foo создаст узел XML <xsd:Foo><xsd:sequence> ... </xsd:sequence></xsd:Foo> с ожидаемыми дочерними атрибутами в определённом порядке.
/ *
* @soap-indicator sequence
* /
class Foo {
...
}
Подробнее об индикаторах soap см. http://www.w3schools.com/schema/schema_complex_indicators.asp. Поскольку вариативность определений WSDL практически неограниченна, для внедрения любого пользовательского XML-строки в сгенерированный файл WSDL можно использовать специальный тег комментариев «@soap-wsdl». Если такой блок кода находится в блоке комментариев класса, то он будет использован вместо разбора и генерации стандартных атрибутов в классе. Это даёт практически неограниченную гибкость в определении структур данных любой сложности. Следующий пример определяет пользовательский фрагмент узла WSDL XML:
/ *
* @soap-wsdl <xsd:sequence>
* @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="name" type="xsd:string"/>
* @soap-wsdl <xsd:choice minOccurs="1" maxOccurs="1" nillable="false">
* @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="age" type="xsd:integer"/>
* @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="date_of_birth" type="xsd:date"/>
* @soap-wsdl </xsd:choice>
* @soap-wsdl </xsd:sequence>
* /
class User {
/ **
* @var string User name {minOccurs=1, maxOccurs=1}
* @soap
* /
public $name;
/ **
* @var integer User age {nillable=0, minOccurs=1, maxOccurs=1}
* @example 35
* @soap
* /
public $age;
/ **
* @var date User's birthday {nillable=0, minOccurs=1, maxOccurs=1}
* @example 1980-05-27
* @soap
* /
public $date_of_birth;
}
В приведенном выше примере генератор WSDL вставит в узел XML <xsd:User> блок кода, определённый строками @soap-wsdl. Вставка в URL SOAP ссылки параметра «?makedoc» позволит генератору WSDL вывести удобное для человека описание всех сложных типов данных вместо файла WSDL XML. Каждый сложный тип описывается в отдельной таблице HTML и также распознаёт тег PHPDoc «@example». См. buildHtmlDocs().
Публичные свойства
| Свойство | Тип | Описание | Определено в |
|---|---|---|---|
| bindingStyle | массив | стиль soap:operation | CWsdlGenerator |
| bindingTransport | строка | транспорт soap:operation | CWsdlGenerator |
| namespace | строка | пространство имён, которое будет использоваться в сгенерированном WSDL. | CWsdlGenerator |
| operationBodyStyle | массив | варианты стиля soap:body operation | CWsdlGenerator |
| serviceName | строка | имя сгенерированного WSDL. | CWsdlGenerator |
Защищённые свойства
| Свойство | Тип | Описание | Определено в |
|---|---|---|---|
| elements | массив | CWsdlGenerator | |
| messages | массив | Карта типов запросов и ответов для всех операций. | CWsdlGenerator |
| operations | массив | Список распознанных SOAP-операций, которые станут удалённо доступными. | CWsdlGenerator |
| typeMap | CWsdlGenerator | ||
| types | массив | Список сложных типов, используемых операциями. | CWsdlGenerator |
Общедоступные методы
| Метод | Описание | Определено в |
|---|---|---|
| __call() | Вызывает метод с указанным именем, который не является методом класса. | CComponent |
| __get() | Возвращает значение свойства, список обработчиков событий или поведение на основе его имени. | CComponent |
| __isset() | Проверяет, является ли значение свойства null. | CComponent |
| __set() | Устанавливает значение свойства компонента. | CComponent |
| __unset() | Устанавливает свойство компонента в null. | CComponent |
| asa() | Возвращает объект поведения с указанным именем. | CComponent |
| attachBehavior() | Прикрепляет поведение к этому компоненту. | CComponent |
| attachBehaviors() | Прикрепляет список поведений к компоненту. | CComponent |
| attachEventHandler() | Прикрепляет обработчик событий к событию. | CComponent |
| buildHtmlDocs() | Генерирует удобную для человека HTML-документацию для сложных типов данных. | CWsdlGenerator |
| canGetProperty() | Определяет, может ли свойство быть прочитано. | CComponent |
| canSetProperty() | Определяет, может ли свойство быть установлено. | CComponent |
| detachBehavior() | Открепляет поведение от компонента. | CComponent |
| detachBehaviors() | Открепляет все поведения от компонента. | CComponent |
| detachEventHandler() | Открепляет существующий обработчик событий. | CComponent |
| disableBehavior() | Отключает прикрепленное поведение. | CComponent |
| disableBehaviors() | Отключает все прикрепленные поведения к этому компоненту. | CComponent |
| enableBehavior() | Включает прикрепленное поведение. | CComponent |
| enableBehaviors() | Включает все прикрепленные поведения к этому компоненту. | CComponent |
| evaluateExpression() | Вычисляет выражение PHP или обратный вызов в контексте этого компонента. | CComponent |
| generateWsdl() | Генерирует WSDL для данного класса. | CWsdlGenerator |
| getEventHandlers() | Возвращает список прикрепленных обработчиков событий для события. | CComponent |
| hasEvent() | Определяет, определено ли событие. | CComponent |
| hasEventHandler() | Проверяет, есть ли прикрепленные обработчики для события с заданным именем. | CComponent |
| hasProperty() | Определяет, определено ли свойство. | CComponent |
| raiseEvent() | Вызывает событие. | CComponent |
Защищенные методы
| Метод | Описание | Определено в |
|---|---|---|
| addBindings() | CWsdlGenerator | |
| addMessages() | CWsdlGenerator | |
| addPortTypes() | CWsdlGenerator | |
| addService() | CWsdlGenerator | |
| addTypes() | CWsdlGenerator | |
| buildDOM() | CWsdlGenerator | |
| createOperationElement() | CWsdlGenerator | |
| createPortElement() | CWsdlGenerator | |
| getWsdlElementAttributes() | Парсинг атрибутов nillable, minOccurs, maxOccurs | CWsdlGenerator |
| injectDom() | Импортирует пользовательский узел XML-источника в документ WSDL под указанный целевой узел. | CWsdlGenerator |
| processMethod() | CWsdlGenerator | |
| processType() | CWsdlGenerator |
Подробное описание свойств
bindingStyle свойство
public array $bindingStyle;
стиль soap:operation
bindingTransport свойство
public string $bindingTransport;
транспорт soap:operation
elements свойство
protected array $elements;
messages свойство
protected array $messages;
Карта типов запроса и ответа для всех операций.
namespace свойство
public string $namespace;
пространство имен, используемое в сгенерированном WSDL. Если не задано, по умолчанию используется имя класса, для которого генерируется WSDL.
operationBodyStyle свойство
public array $operationBodyStyle;
варианты стиля soap:body операции
operations свойство
protected array $operations;
Список распознанных операций SOAP, которые будут доступны удаленно. Все методы с объявленным параметром @soap будут включены здесь в формате operation1 => description1, operation2 => description2, ..
serviceName свойство
public string $serviceName;
имя сгенерированного WSDL. Если не задано, по умолчанию используется "urn:{$className}wsdl".
typeMap свойство
protected static $typeMap;
types свойство
protected array $types;
Список сложных типов, используемых операциями. Если операция SOAP определяет сложный тип входных или выходных данных, все объекты включаются сюда, содержащие все подпараметры. Например, если операция SOAP «createUser» требует сложный объект «User», то объект «User» будет включен сюда с объявленными подпараметрами, такими как «firstname», «lastname» и т. д..
Подробное описание методов
addBindings() метод
protected void addBindings(DOMDocument $dom) | ||
| $dom | DOMDocument | Представляет весь документ HTML или XML; служит корнем дерева документа |
protected function addBindings($dom)
{
$binding=$dom->createElement('wsdl:binding');
$binding->setAttribute('name',$this->serviceName.'Binding');
$binding->setAttribute('type','tns:'.$this->serviceName.'PortType');
$soapBinding=$dom->createElement('soap:binding');
$soapBinding->setAttribute('style',$this->bindingStyle);
$soapBinding->setAttribute('transport',$this->bindingTransport);
$binding->appendChild($soapBinding);
$dom->documentElement->appendChild($binding);
foreach($this->operations as $name=>$operation)
$binding->appendChild($this->createOperationElement($dom,$name,$operation['headers']));
}
addMessages() метод
protected void addMessages(DOMDocument $dom) | ||
| $dom | DOMDocument | Представляет весь документ HTML или XML; служит корнем дерева документа |
protected function addMessages($dom)
{
foreach($this->messages as $name=>$message)
{
$element=$dom->createElement('wsdl:message');
$element->setAttribute('name',$name);
foreach($this->messages[$name] as $partName=>$part)
{
if(is_array($part))
{
$partElement=$dom->createElement('wsdl:part');
$partElement->setAttribute('name',$partName);
if (isset($part['type']))
{
$partElement->setAttribute('type',$part['type']);
}
if (isset($part['element']))
{
$partElement->setAttribute('element',$part['element']);
}
$element->appendChild($partElement);
}
}
$dom->documentElement->appendChild($element);
}
}
addPortTypes() метод
protected void addPortTypes(DOMDocument $dom) | ||
| $dom | DOMDocument | Представляет весь HTML- или XML-документ; служит корнем дерева документа |
protected function addPortTypes($dom)
{
$portType=$dom->createElement('wsdl:portType');
$portType->setAttribute('name',$this->serviceName.'PortType');
$dom->documentElement->appendChild($portType);
foreach($this->operations as $name=>$operation)
$portType->appendChild($this->createPortElement($dom,$name,$operation['doc']));
}
addService() метод
protected void addService(DOMDocument $dom, string $serviceUrl) | ||
| $dom | DOMDocument | Представляет весь HTML- или XML-документ; служит корнем дерева документа |
| $serviceUrl | string | URL веб-сервиса |
protected function addService($dom,$serviceUrl)
{
$service=$dom->createElement('wsdl:service');
$service->setAttribute('name', $this->serviceName.'Service');
$port=$dom->createElement('wsdl:port');
$port->setAttribute('name', $this->serviceName.'Port');
$port->setAttribute('binding', 'tns:'.$this->serviceName.'Binding');
$soapAddress=$dom->createElement('soap:address');
$soapAddress->setAttribute('location',$serviceUrl);
$port->appendChild($soapAddress);
$service->appendChild($port);
$dom->documentElement->appendChild($service);
}
addTypes() метод
protected void addTypes(DOMDocument $dom) | ||
| $dom | DOMDocument | Представляет весь HTML- или XML-документ; служит корнем дерева документа |
protected function addTypes($dom)
{
if($this->types===array() && $this->elements===array())
return;
$types=$dom->createElement('wsdl:types');
$schema=$dom->createElement('xsd:schema');
$schema->setAttribute('targetNamespace',$this->namespace);
foreach($this->types as $phpType=>$xmlType)
{
if(is_string($xmlType) && strrpos($xmlType,'Array')!==strlen($xmlType)-5)
continue; // simple type
$complexType=$dom->createElement('xsd:complexType');
if(is_string($xmlType))
{
if(($pos=strpos($xmlType,'tns:'))!==false)
$complexType->setAttribute('name',substr($xmlType,4));
else
$complexType->setAttribute('name',$xmlType);
$arrayType = ($dppos=strpos($xmlType,':')) !==false ? substr($xmlType,$dppos + 1) : $xmlType; // strip namespace, if any
$arrayType = substr($arrayType,0,-5); // strip 'Array' from name
if ($this->operationBodyStyle['use'] == self::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('wsdl: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))
{
$complexType->setAttribute('name',$phpType);
if($xmlType['custom_wsdl']!==false)
{
$custom_dom=new DOMDocument();
$custom_dom->loadXML('<root xmlns:xsd="http://www.w3.org/2001/XMLSchema">'.$xmlType['custom_wsdl'].'</root>');
foreach($custom_dom->documentElement->childNodes as $el)
$this->injectDom($dom,$complexType,$el);
}else{
$all=$dom->createElement('xsd:' . $xmlType['indicator']);
if(!is_null($xmlType['minOccurs']))
$all->setAttribute('minOccurs',$xmlType['minOccurs']);
if(!is_null($xmlType['maxOccurs']))
$all->setAttribute('maxOccurs',$xmlType['maxOccurs']);
if(!is_null($xmlType['nillable']))
$all->setAttribute('nillable',$xmlType['nillable']);
foreach($xmlType['properties'] as $name=>$type)
{
$element=$dom->createElement('xsd:element');
if(!is_null($type[3]))
$element->setAttribute('minOccurs',$type[3]);
if(!is_null($type[4]))
$element->setAttribute('maxOccurs',$type[4]);
if(!is_null($type[2]))
$element->setAttribute('nillable',$type[2]);
$element->setAttribute('name',$name);
$element->setAttribute('type',$type[0]);
$all->appendChild($element);
}
$complexType->appendChild($all);
}
}
$schema->appendChild($complexType);
}
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);
}
buildDOM() метод
protected DOMDocument buildDOM(string $serviceUrl, string $encoding) | ||
| $serviceUrl | string | URL веб-сервиса |
| $encoding | string | кодировка WSDL. По умолчанию 'UTF-8'. |
| {return} | DOMDocument | |
protected function buildDOM($serviceUrl,$encoding)
{
$xml="<?xml version=\"1.0\" encoding=\"$encoding\"?>
<definitions name=\"{$this->serviceName}\" targetNamespace=\"{$this->namespace}\"
xmlns=\"http://schemas.xmlsoap.org/wsdl/\"
xmlns:tns=\"{$this->namespace}\"
xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\"
xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"></definitions>";
$dom=new DOMDocument();
$dom->formatOutput=true;
$dom->loadXml($xml);
$this->addTypes($dom);
$this->addMessages($dom);
$this->addPortTypes($dom);
$this->addBindings($dom);
$this->addService($dom,$serviceUrl);
return $dom;
}
buildHtmlDocs() метод
public void buildHtmlDocs(bool $return=false) | ||
| $return | bool | Если true, сгенерированный HTML-вывод будет возвращён, а не отправлен напрямую в буфер вывода |
public function buildHtmlDocs($return=false)
{
$html='<html><head>';
$html.='<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
$html.='<style type="text/css">
table{border-collapse: collapse;background-color: #DDDDDD;}
tr{background-color: #FFFFFF;}
th{background-color: #EEEEEE;}
th, td{font-size: 12px;font-family: courier;padding: 3px;}
</style>';
$html.='</head><body>';
$html.='<h2>WSDL documentation for service '.$this->serviceName.'</h2>';
$html.='<p>Generated on '.date('d.m.Y H:i:s').'</p>';
$html.='<table border="0" cellspacing="1" cellpadding="1">';
$html.='<tr><td>';
if(!empty($this->types))
{
foreach($this->types as $object=>$options){
if(!is_array($options) || empty($options) || !is_array($options['properties']) || empty($options['properties'])){
continue;
}
$params=$options['properties'];
$html.="\n\n<h3>Object: {$object}</h3>";
$html.='<table border="1" cellspacing="1" cellpadding="1">';
$html.='<tr><th>#</th><th>Attribute</th><th>Type</th><th>Nill</th><th>Min</th><th>Max</th><th>Description</th><th>Example</th></tr>';
$c=0;
foreach($params as $param=>$prop){
++$c;
$html.="\n<tr>"
."\n\t<td>{$c}</td>"
."\n\t<td>{$param}</td>"
."\n\t<td>".(str_replace('xsd:','',$prop[0]))."</td>"
."\n\t<td>".$prop[2]."</td>"
."\n\t<td>".($prop[3]==null ? ' ' : $prop[3])."</td>"
."\n\t<td>".($prop[4]==null ? ' ' : $prop[4])."</td>"
."\n\t<td>{$prop[1]}</td>"
."\n\t<td>".(trim($prop[5])=='' ? ' ' : $prop[5])."</td>"
."\n</tr>";
}
$html.="\n</table><br/>";
}
}
else
$html.='No complex data type found!';
$html.='</td></tr></table></body></html>';
if($return)
return $html;
echo $html;
Yii::app()->end(); // end the app to avoid conflict with text/xml header
}
Генерирует удобочитаемую HTML-документацию для сложных типов данных. Этот метод можно вызвать, добавив параметр URL «&makedoc» в ссылку URL, например, «http://www.mydomain.com/soap/create?makedoc», или просто вызвав из другого скрипта с аргументом $return=true.
Каждый сложный тип данных описывается в отдельной HTML-таблице, содержащей следующие столбцы:
- # - идентификатор атрибута
- Атрибут - имя атрибута, например, firstname
- Тип - тип атрибута, например, integer, date, tns:SoapPovCalculationResultArray
- Nill - true|false - указывает, является ли атрибут необязательным
- Min - минимальное количество повторений
- Max - максимальное количество повторений
- Описание - Подробное описание атрибута.
- Пример - Пример значения атрибута, если он предоставлен через PHPDoc свойство @example.
createOperationElement() метод
protected DOMElement createOperationElement(DOMDocument $dom, string $name, array $headers=NULL) | ||
| $dom | DOMDocument | Представляет весь HTML- или XML-документ; служит корнем дерева документа |
| $name | string | имя метода |
| $headers | array | массив типа array('input'=>array(MESSAGE,PART),'output=>array(MESSAGE,PART)) |
| {return} | DOMElement | новая instance элемента wsdl:operation |
protected function createOperationElement($dom,$name,$headers=null)
{
$operation=$dom->createElement('wsdl:operation');
$operation->setAttribute('name', $name);
$soapOperation=$dom->createElement('soap:operation');
$soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name);
if ($this->bindingStyle == self::STYLE_RPC)
{
$soapOperation->setAttribute('style', self::STYLE_RPC);
}
$input=$dom->createElement('wsdl:input');
$output=$dom->createElement('wsdl:output');
$soapBody=$dom->createElement('soap:body');
$operationBodyStyle=$this->operationBodyStyle;
if ($this->bindingStyle == self::STYLE_RPC && !isset($operationBodyStyle['namespace']))
{
$operationBodyStyle['namespace'] = $this->namespace;
}
foreach($operationBodyStyle as $attributeName=>$attributeValue)
{
$soapBody->setAttribute($attributeName, $attributeValue);
}
$input->appendChild($soapBody);
$output->appendChild(clone $soapBody);
if (is_array($headers))
{
if (isset($headers['input']) && is_array($headers['input']) && count($headers['input'])==2)
{
$soapHeader = $dom->createElement('soap:header');
foreach($operationBodyStyle as $attributeName=>$attributeValue) {
$soapHeader->setAttribute($attributeName, $attributeValue);
}
$soapHeader->setAttribute('message', $headers['input'][0]);
$soapHeader->setAttribute('part', $headers['input'][1]);
$input->appendChild($soapHeader);
}
if (isset($headers['output']) && is_array($headers['output']) && count($headers['output'])==2)
{
$soapHeader = $dom->createElement('soap:header');
foreach($operationBodyStyle as $attributeName=>$attributeValue) {
$soapHeader->setAttribute($attributeName, $attributeValue);
}
$soapHeader->setAttribute('message', $headers['output'][0]);
$soapHeader->setAttribute('part', $headers['output'][1]);
$output->appendChild($soapHeader);
}
}
$operation->appendChild($soapOperation);
$operation->appendChild($input);
$operation->appendChild($output);
return $operation;
}
createPortElement() метод
protected DOMElement createPortElement(DOMDocument $dom, string $name, string $doc) | ||
| $dom | DOMDocument | Представляет весь HTML- или XML-документ; служит корнем дерева документа |
| $name | string | имя метода |
| $doc | string | doc |
| {return} | DOMElement | новый экземпляр элемента wsdl:operation, заполненный данными tns In/Out |
protected function createPortElement($dom,$name,$doc)
{
$operation=$dom->createElement('wsdl:operation');
$operation->setAttribute('name',$name);
$input=$dom->createElement('wsdl:input');
$input->setAttribute('message', 'tns:'.$name.'In');
$output=$dom->createElement('wsdl:output');
$output->setAttribute('message', 'tns:'.$name.'Out');
$operation->appendChild($dom->createElement('wsdl:documentation',$doc));
$operation->appendChild($input);
$operation->appendChild($output);
return $operation;
}
generateWsdl() метод
public string generateWsdl(string $className, string $serviceUrl, string $encoding='UTF-8') | ||
| $className | string | имя класса |
| $serviceUrl | string | URL веб-сервиса |
| $encoding | string | кодировка WSDL. По умолчанию 'UTF-8'. |
| {return} | string | сгенерированный WSDL |
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;
if($this->namespace===null)
$this->namespace='urn:'.str_replace('\\','/',$className).'wsdl';
$reflection=new ReflectionClass($className);
foreach($reflection->getMethods() as $method)
{
if($method->isPublic())
$this->processMethod($method);
}
$wsdl=$this->buildDOM($serviceUrl,$encoding)->saveXML();
if(isset($_GET['makedoc']))
$this->buildHtmlDocs();
return $wsdl;
}
Генерирует WSDL для данного класса.
getWsdlElementAttributes() метод
protected array getWsdlElementAttributes(string $comment) | ||
| $comment | string | Извлечённый комментарий PHPDoc |
| {return} | array | массив [nillable, minOccurs, maxOccurs] |
protected function getWsdlElementAttributes($comment) {
$nillable=$minOccurs=$maxOccurs=null;
if(preg_match('/{(.+)}/',$comment,$attr))
{
if(preg_match_all('/((\w+)\s*=\s*(\w+))/mi',$attr[1],$attr))
{
foreach($attr[2] as $id=>$prop)
{
$prop=strtolower($prop);
$val=strtolower($attr[3][$id]);
if($prop=='nillable'){
if($val=='false' || $val=='true')
$nillable=$val;
else
$nillable=$val ? 'true' : 'false';
}elseif($prop=='minoccurs')
$minOccurs=intval($val);
elseif($prop=='maxoccurs')
$maxOccurs=($val=='unbounded') ? 'unbounded' : intval($val);
}
}
}
return array(
'nillable'=>$nillable,
'minOccurs'=>$minOccurs,
'maxOccurs'=>$maxOccurs
);
}
Разбор атрибутов nillable, minOccurs, maxOccurs
injectDom() метод
protected void injectDom(DOMDocument $dom, DOMElement $target, DOMNode $source) | ||
| $dom | DOMDocument | Генерируемый XML-документ WSDL |
| $target | DOMElement | XML-узел, в который будет добавлен узел $source |
| $source | DOMNode | Исходный XML-узел, который будет импортирован |
protected function injectDom(DOMDocument $dom, DOMElement $target, DOMNode $source)
{
if ($source->nodeType!=XML_ELEMENT_NODE)
return;
$import=$dom->createElement($source->nodeName);
foreach($source->attributes as $attr)
$import->setAttribute($attr->name,$attr->value);
foreach($source->childNodes as $child)
$this->injectDom($dom,$import,$child);
$target->appendChild($import);
}
Импорт пользовательского XML-узла в WSDL-документ под указанным узлом-целью
processMethod() метод
protected void processMethod(ReflectionMethod $method) | ||
| $method | ReflectionMethod | метод |
protected function processMethod($method)
{
$comment=$method->getDocComment();
if(strpos($comment,'@soap')===false)
return;
$comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n")); // make line endings consistent: win -> unix, mac -> unix
$methodName=$method->getName();
$comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
$params=$method->getParameters();
$message=array();
$headers=array();
$n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
if($n>count($params))
$n=count($params);
if ($this->bindingStyle == self::STYLE_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.'In']=$message;
$n=preg_match_all('/^@header\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
for($i=0;$i<$n;++$i)
{
$name = $matches[1][$i];
$type = $this->processType($matches[1][$i]);
$doc = trim($matches[3][$i]);
if ($this->bindingStyle == self::STYLE_RPC)
{
$headers[$name]=array($type,$doc);
}
else
{
$this->elements[$name][$name]=array('type'=>$type);
$headers[$name] = array('element'=>$type);
}
}
if ($headers !== array())
{
$this->messages[$methodName.'Headers']=$headers;
$headerKeys = array_keys($headers);
$firstHeaderKey = reset($headerKeys);
$firstHeader = $headers[$firstHeaderKey];
}
else
{
$firstHeader = null;
}
if ($this->bindingStyle == self::STYLE_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
$doc='';
$this->operations[$methodName]=array(
'doc'=>$doc,
'headers'=>$firstHeader === null ? null : array('input'=>array($methodName.'Headers', $firstHeaderKey)),
);
}
processType() метод
protected mixed|string processType(string $type) | ||
| $type | string | Тип PHP переменной |
| {return} | mixed|string | |
protected function processType($type)
{
if(isset(self::$typeMap[$type]))
return self::$typeMap[$type];
elseif(isset($this->types[$type]))
return is_array($this->types[$type]) ? 'tns:'.$type : $this->types[$type];
elseif(($pos=strpos($type,'[]'))!==false)
{ // array of types
$type=substr($type,0,$pos);
$this->types[$type.'[]']='tns:'.$type.'Array';
$this->processType($type);
return $this->types[$type.'[]'];
}
else
{ // process class / complex type
$type=Yii::import($type,true);
$class=new ReflectionClass($type);
$comment=$class->getDocComment();
$comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n")); // make line endings consistent: win -> unix, mac -> unix
$comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
// extract soap indicator flag, if defined, e.g. @soap-indicator sequence
// see http://www.w3schools.com/schema/schema_complex_indicators.asp
if(preg_match('/^@soap-indicator\s+(\w+)\s*?(.*)$/im', $comment, $matches))
{
$indicator=$matches[1];
$attributes=$this->getWsdlElementAttributes($matches[2]);
}else{
$indicator='all';
$attributes=$this->getWsdlElementAttributes('');
}
$custom_wsdl=false;
if(preg_match_all('/^@soap-wsdl\s+(\S.*)$/im',$comment,$matches)>0)
$custom_wsdl=implode("\n", $matches[1]);
$this->types[$type]=array(
'indicator'=>$indicator,
'nillable'=>$attributes['nillable'],
'minOccurs'=>$attributes['minOccurs'],
'maxOccurs'=>$attributes['maxOccurs'],
'custom_wsdl'=>$custom_wsdl,
'properties'=>array()
);
foreach($class->getProperties() as $property)
{
$comment=$property->getDocComment();
if($property->isPublic() && strpos($comment,'@soap')!==false)
{
if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/mi',$comment,$matches))
{
$attributes=$this->getWsdlElementAttributes($matches[3]);
if(preg_match('/{(.+)}/',$comment,$attr))
$matches[3]=str_replace($attr[0],'',$matches[3]);
// extract PHPDoc @example
$example='';
if(preg_match("/@example[:]?(.+)/mi",$comment,$match))
$example=trim($match[1]);
$this->types[$type]['properties'][$property->getName()]=array(
$this->processType($matches[1]),
trim($matches[3]),
$attributes['nillable'],
$attributes['minOccurs'],
$attributes['maxOccurs'],
$example
); // name => type, doc, nillable, minOccurs, maxOccurs, example
}
}
}
return 'tns:'.$type;
}
}
© 2008–2017 by Yii Software LLC
Licensed under the three clause BSD license.
http://www.yiiframework.com/doc/api/1.1/CWsdlGenerator