CWsdlGenerator.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <?php
  2. /**
  3. * CWsdlGenerator class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CWsdlGenerator generates the WSDL for a given service class.
  12. *
  13. * The WSDL generation is based on the doc comments found in the service class file.
  14. * In particular, it recognizes the '@soap' tag in the comment and extracts
  15. * API method and type definitions.
  16. *
  17. * In a service class, a remote invokable method must be a public method with a doc
  18. * comment block containing the '@soap' tag. In the doc comment, the type and name
  19. * of every input parameter and the type of the return value should be declared using
  20. * the standard phpdoc format.
  21. *
  22. * CWsdlGenerator recognizes the following primitive types (case-sensitive) in
  23. * the parameter and return type declarations:
  24. * <ul>
  25. * <li>str/string: maps to xsd:string;</li>
  26. * <li>int/integer: maps to xsd:int;</li>
  27. * <li>float/double: maps to xsd:float;</li>
  28. * <li>bool/boolean: maps to xsd:boolean;</li>
  29. * <li>date: maps to xsd:date;</li>
  30. * <li>time: maps to xsd:time;</li>
  31. * <li>datetime: maps to xsd:dateTime;</li>
  32. * <li>array: maps to xsd:string;</li>
  33. * <li>object: maps to xsd:struct;</li>
  34. * <li>mixed: maps to xsd:anyType.</li>
  35. * </ul>
  36. *
  37. * If a type is not a primitive type, it is considered as a class type, and
  38. * CWsdlGenerator will look for its property declarations. Only public properties
  39. * are considered, and they each must be associated with a doc comment block containg
  40. * the '@soap' tag. The doc comment block should declare the type of the property.
  41. *
  42. * CWsdlGenerator recognizes the array type with the following format:
  43. * <pre>
  44. * typeName[]: maps to tns:typeNameArray
  45. * </pre>
  46. *
  47. * The following is an example declaring a remote invokable method:
  48. * <pre>
  49. * / **
  50. * * A foo method.
  51. * * @param string name of something
  52. * * @param string value of something
  53. * * @return string[] some array
  54. * * @soap
  55. * * /
  56. * public function foo($name,$value) {...}
  57. * </pre>
  58. *
  59. * And the following is an example declaring a class with remote accessible properties:
  60. * <pre>
  61. * class Foo {
  62. * / **
  63. * * @var string name of foo {nillable=1, minOccurs=0, maxOccurs=2}
  64. * * @soap
  65. * * /
  66. * public $name;
  67. * / **
  68. * * @var Member[] members of foo
  69. * * @soap
  70. * * /
  71. * public $members;
  72. * }
  73. * </pre>
  74. * In the above, the 'members' property is an array of 'Member' objects. Since 'Member' is not
  75. * a primitive type, CWsdlGenerator will look further to find the definition of 'Member'.
  76. *
  77. * Optionally, extra attributes (nillable, minOccurs, maxOccurs) can be defined for each
  78. * property by enclosing definitions into curly brackets and separated by comma like so:
  79. *
  80. * {[attribute1 = value1][, attribute2 = value2], ...}
  81. *
  82. * where the attribute can be one of following:
  83. * <ul>
  84. * <li>nillable = [0|1|true|false]</li>
  85. * <li>minOccurs = n; where n>=0</li>
  86. * <li>maxOccurs = n; where [n>=0|unbounded]</li>
  87. * </ul>
  88. *
  89. * Additionally, each complex data type can have assigned a soap indicator flag declaring special usage for such a data type.
  90. * A soap indicator must be declared in the doc comment block with the '@soap-indicator' tag.
  91. * Following soap indicators are currently supported:
  92. * <ul>
  93. * <li>all - (default) allows any sorting order of child nodes</li>
  94. * <li>sequence - all child nodes in WSDL XML file will be expected in predefined order</li>
  95. * <li>choice - supplied can be either of the child elements</li>
  96. * </ul>
  97. * The Group indicators can be also injected via custom soap definitions as XML node into WSDL structure.
  98. *
  99. * In the following example, class Foo will create a XML node &lt;xsd:Foo&gt;&lt;xsd:sequence&gt; ... &lt;/xsd:sequence&gt;&lt;/xsd:Foo&gt; with children attributes expected in pre-defined order.
  100. * <pre>
  101. * / *
  102. * * @soap-indicator sequence
  103. * * /
  104. * class Foo {
  105. * ...
  106. * }
  107. * </pre>
  108. * For more on soap indicators, see See {@link http://www.w3schools.com/schema/schema_complex_indicators.asp}.
  109. *
  110. * Since the variability of WSDL definitions is virtually unlimited, a special doc comment tag '@soap-wsdl' can be used in order to inject any custom XML string into generated WSDL file.
  111. * If such a block of the code is found in class's comment block, then it will be used instead of parsing and generating standard attributes within the class.
  112. * This gives virtually unlimited flexibility in defining data structures of any complexity.
  113. * Following is an example of defining custom piece of WSDL XML node:
  114. * <pre>
  115. * / *
  116. * * @soap-wsdl <xsd:sequence>
  117. * * @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="name" type="xsd:string"/>
  118. * * @soap-wsdl <xsd:choice minOccurs="1" maxOccurs="1" nillable="false">
  119. * * @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="age" type="xsd:integer"/>
  120. * * @soap-wsdl <xsd:element minOccurs="1" maxOccurs="1" nillable="false" name="date_of_birth" type="xsd:date"/>
  121. * * @soap-wsdl </xsd:choice>
  122. * * @soap-wsdl </xsd:sequence>
  123. * * /
  124. * class User {
  125. * / **
  126. * * @var string User name {minOccurs=1, maxOccurs=1}
  127. * * @soap
  128. * * /
  129. * public $name;
  130. * / **
  131. * * @var integer User age {nillable=0, minOccurs=1, maxOccurs=1}
  132. * * @example 35
  133. * * @soap
  134. * * /
  135. * public $age;
  136. * / **
  137. * * @var date User's birthday {nillable=0, minOccurs=1, maxOccurs=1}
  138. * * @example 1980-05-27
  139. * * @soap
  140. * * /
  141. * public $date_of_birth;
  142. * }
  143. * </pre>
  144. * In the example above, WSDL generator would inject under XML node &lt;xsd:User&gt; the code block defined by @soap-wsdl lines.
  145. *
  146. * By inserting into SOAP URL link the parameter "?makedoc", WSDL generator will output human-friendly overview of all complex data types rather than XML WSDL file.
  147. * Each complex type is described in a separate HTML table and recognizes also the '@example' PHPDoc tag. See {@link buildHtmlDocs()}.
  148. *
  149. * @author Qiang Xue <qiang.xue@gmail.com>
  150. * @package system.web.services
  151. * @since 1.0
  152. */
  153. class CWsdlGenerator extends CComponent
  154. {
  155. const STYLE_RPC = 'rpc';
  156. const STYLE_DOCUMENT = 'document';
  157. const USE_ENCODED = 'encoded';
  158. const USE_LITERAL = 'literal';
  159. /**
  160. * @var string the namespace to be used in the generated WSDL.
  161. * If not set, it defaults to the name of the class that WSDL is generated upon.
  162. */
  163. public $namespace;
  164. /**
  165. * @var string the name of the generated WSDL.
  166. * If not set, it defaults to "urn:{$className}wsdl".
  167. */
  168. public $serviceName;
  169. /**
  170. * @var array
  171. * soap:body operation style options
  172. */
  173. public $operationBodyStyle = array(
  174. 'use' => self::USE_ENCODED,
  175. 'encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/',
  176. );
  177. /**
  178. * @var array
  179. * soap:operation style
  180. */
  181. public $bindingStyle = self::STYLE_RPC;
  182. /**
  183. * @var string
  184. * soap:operation transport
  185. */
  186. public $bindingTransport = 'http://schemas.xmlsoap.org/soap/http';
  187. protected static $typeMap=array(
  188. 'string'=>'xsd:string',
  189. 'str'=>'xsd:string',
  190. 'int'=>'xsd:int',
  191. 'integer'=>'xsd:integer',
  192. 'float'=>'xsd:float',
  193. 'double'=>'xsd:float',
  194. 'bool'=>'xsd:boolean',
  195. 'boolean'=>'xsd:boolean',
  196. 'date'=>'xsd:date',
  197. 'time'=>'xsd:time',
  198. 'datetime'=>'xsd:dateTime',
  199. 'array'=>'soap-enc:Array',
  200. 'object'=>'xsd:struct',
  201. 'mixed'=>'xsd:anyType',
  202. );
  203. /**
  204. * @var array List of recognized SOAP operations that will become remotely available.
  205. * All methods with declared @soap parameter will be included here in the format operation1 => description1, operation2 => description2, ..
  206. */
  207. protected $operations;
  208. /**
  209. * @var array List of complex types used by operations.
  210. * If an SOAP operation defines complex input or output type, all objects are included here containing all sub-parameters.
  211. * For instance, if an SOAP operation "createUser" requires complex input object "User", then the object "User" will be included here with declared subparameters such as "firstname", "lastname", etc..
  212. */
  213. protected $types;
  214. /**
  215. * @var array
  216. */
  217. protected $elements;
  218. /**
  219. * @var array Map of request and response types for all operations.
  220. */
  221. protected $messages;
  222. /**
  223. * Generates the WSDL for the given class.
  224. * @param string $className class name
  225. * @param string $serviceUrl Web service URL
  226. * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'.
  227. * @return string the generated WSDL
  228. */
  229. public function generateWsdl($className, $serviceUrl, $encoding='UTF-8')
  230. {
  231. $this->operations=array();
  232. $this->types=array();
  233. $this->elements=array();
  234. $this->messages=array();
  235. if($this->serviceName===null)
  236. $this->serviceName=$className;
  237. if($this->namespace===null)
  238. $this->namespace='urn:'.str_replace('\\','/',$className).'wsdl';
  239. $reflection=new ReflectionClass($className);
  240. foreach($reflection->getMethods() as $method)
  241. {
  242. if($method->isPublic())
  243. $this->processMethod($method);
  244. }
  245. $wsdl=$this->buildDOM($serviceUrl,$encoding)->saveXML();
  246. if(isset($_GET['makedoc']))
  247. $this->buildHtmlDocs();
  248. return $wsdl;
  249. }
  250. /**
  251. * @param ReflectionMethod $method method
  252. */
  253. protected function processMethod($method)
  254. {
  255. $comment=$method->getDocComment();
  256. if(strpos($comment,'@soap')===false)
  257. return;
  258. $comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n")); // make line endings consistent: win -> unix, mac -> unix
  259. $methodName=$method->getName();
  260. $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
  261. $params=$method->getParameters();
  262. $message=array();
  263. $headers=array();
  264. $n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
  265. if($n>count($params))
  266. $n=count($params);
  267. if ($this->bindingStyle == self::STYLE_RPC)
  268. {
  269. for($i=0;$i<$n;++$i)
  270. $message[$params[$i]->getName()]=array(
  271. 'type'=>$this->processType($matches[1][$i]),
  272. 'doc'=>trim($matches[3][$i]),
  273. );
  274. }
  275. else
  276. {
  277. $this->elements[$methodName] = array();
  278. for($i=0;$i<$n;++$i)
  279. $this->elements[$methodName][$params[$i]->getName()]=array(
  280. 'type'=>$this->processType($matches[1][$i]),
  281. 'nillable'=>$params[$i]->isOptional(),
  282. );
  283. $message['parameters'] = array('element'=>'tns:'.$methodName);
  284. }
  285. $this->messages[$methodName.'In']=$message;
  286. $n=preg_match_all('/^@header\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
  287. for($i=0;$i<$n;++$i)
  288. {
  289. $name = $matches[1][$i];
  290. $type = $this->processType($matches[1][$i]);
  291. $doc = trim($matches[3][$i]);
  292. if ($this->bindingStyle == self::STYLE_RPC)
  293. {
  294. $headers[$name]=array($type,$doc);
  295. }
  296. else
  297. {
  298. $this->elements[$name][$name]=array('type'=>$type);
  299. $headers[$name] = array('element'=>$type);
  300. }
  301. }
  302. if ($headers !== array())
  303. {
  304. $this->messages[$methodName.'Headers']=$headers;
  305. $headerKeys = array_keys($headers);
  306. $firstHeaderKey = reset($headerKeys);
  307. $firstHeader = $headers[$firstHeaderKey];
  308. }
  309. else
  310. {
  311. $firstHeader = null;
  312. }
  313. if ($this->bindingStyle == self::STYLE_RPC)
  314. {
  315. if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
  316. $return=array(
  317. 'type'=>$this->processType($matches[1]),
  318. 'doc'=>trim($matches[2]),
  319. );
  320. else
  321. $return=null;
  322. $this->messages[$methodName.'Out']=array('return'=>$return);
  323. }
  324. else
  325. {
  326. if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
  327. {
  328. $this->elements[$methodName.'Response'][$methodName.'Result']=array(
  329. 'type'=>$this->processType($matches[1]),
  330. );
  331. }
  332. $this->messages[$methodName.'Out']=array('parameters'=>array('element'=>'tns:'.$methodName.'Response'));
  333. }
  334. if(preg_match('/^\/\*+\s*([^@]*?)\n@/s',$comment,$matches))
  335. $doc=trim($matches[1]);
  336. else
  337. $doc='';
  338. $this->operations[$methodName]=array(
  339. 'doc'=>$doc,
  340. 'headers'=>$firstHeader === null ? null : array('input'=>array($methodName.'Headers', $firstHeaderKey)),
  341. );
  342. }
  343. /**
  344. * @param string $type PHP variable type
  345. */
  346. protected function processType($type)
  347. {
  348. if(isset(self::$typeMap[$type]))
  349. return self::$typeMap[$type];
  350. elseif(isset($this->types[$type]))
  351. return is_array($this->types[$type]) ? 'tns:'.$type : $this->types[$type];
  352. elseif(($pos=strpos($type,'[]'))!==false)
  353. { // array of types
  354. $type=substr($type,0,$pos);
  355. $this->types[$type.'[]']='tns:'.$type.'Array';
  356. $this->processType($type);
  357. return $this->types[$type.'[]'];
  358. }
  359. else
  360. { // process class / complex type
  361. $type=Yii::import($type,true);
  362. $class=new ReflectionClass($type);
  363. $comment=$class->getDocComment();
  364. $comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n")); // make line endings consistent: win -> unix, mac -> unix
  365. $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
  366. // extract soap indicator flag, if defined, e.g. @soap-indicator sequence
  367. // see http://www.w3schools.com/schema/schema_complex_indicators.asp
  368. if(preg_match('/^@soap-indicator\s+(\w+)\s*?(.*)$/im', $comment, $matches))
  369. {
  370. $indicator=$matches[1];
  371. $attributes=$this->getWsdlElementAttributes($matches[2]);
  372. }else{
  373. $indicator='all';
  374. $attributes=$this->getWsdlElementAttributes('');
  375. }
  376. $custom_wsdl=false;
  377. if(preg_match_all('/^@soap-wsdl\s+(\S.*)$/im',$comment,$matches)>0)
  378. $custom_wsdl=implode("\n", $matches[1]);
  379. $this->types[$type]=array(
  380. 'indicator'=>$indicator,
  381. 'nillable'=>$attributes['nillable'],
  382. 'minOccurs'=>$attributes['minOccurs'],
  383. 'maxOccurs'=>$attributes['maxOccurs'],
  384. 'custom_wsdl'=>$custom_wsdl,
  385. 'properties'=>array()
  386. );
  387. foreach($class->getProperties() as $property)
  388. {
  389. $comment=$property->getDocComment();
  390. if($property->isPublic() && strpos($comment,'@soap')!==false)
  391. {
  392. if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/mi',$comment,$matches))
  393. {
  394. $attributes=$this->getWsdlElementAttributes($matches[3]);
  395. if(preg_match('/{(.+)}/',$comment,$attr))
  396. $matches[3]=str_replace($attr[0],'',$matches[3]);
  397. // extract PHPDoc @example
  398. $example='';
  399. if(preg_match("/@example[:]?(.+)/mi",$comment,$match))
  400. $example=trim($match[1]);
  401. $this->types[$type]['properties'][$property->getName()]=array(
  402. $this->processType($matches[1]),
  403. trim($matches[3]),
  404. $attributes['nillable'],
  405. $attributes['minOccurs'],
  406. $attributes['maxOccurs'],
  407. $example
  408. ); // name => type, doc, nillable, minOccurs, maxOccurs, example
  409. }
  410. }
  411. }
  412. return 'tns:'.$type;
  413. }
  414. }
  415. /**
  416. * Parse attributes nillable, minOccurs, maxOccurs
  417. * @param string $comment Extracted PHPDoc comment
  418. */
  419. protected function getWsdlElementAttributes($comment) {
  420. $nillable=$minOccurs=$maxOccurs=null;
  421. if(preg_match('/{(.+)}/',$comment,$attr))
  422. {
  423. if(preg_match_all('/((\w+)\s*=\s*(\w+))/mi',$attr[1],$attr))
  424. {
  425. foreach($attr[2] as $id=>$prop)
  426. {
  427. $prop=strtolower($prop);
  428. $val=strtolower($attr[3][$id]);
  429. if($prop=='nillable'){
  430. if($val=='false' || $val=='true')
  431. $nillable=$val;
  432. else
  433. $nillable=$val ? 'true' : 'false';
  434. }elseif($prop=='minoccurs')
  435. $minOccurs=intval($val);
  436. elseif($prop=='maxoccurs')
  437. $maxOccurs=($val=='unbounded') ? 'unbounded' : intval($val);
  438. }
  439. }
  440. }
  441. return array(
  442. 'nillable'=>$nillable,
  443. 'minOccurs'=>$minOccurs,
  444. 'maxOccurs'=>$maxOccurs
  445. );
  446. }
  447. /**
  448. * Import custom XML source node into WSDL document under specified target node
  449. * @param DOMDocument $dom XML WSDL document being generated
  450. * @param DOMElement $target XML node, to which will be appended $source node
  451. * @param DOMNode $source Source XML node to be imported
  452. */
  453. protected function injectDom(DOMDocument $dom, DOMElement $target, DOMNode $source)
  454. {
  455. if ($source->nodeType!=XML_ELEMENT_NODE)
  456. return;
  457. $import=$dom->createElement($source->nodeName);
  458. foreach($source->attributes as $attr)
  459. $import->setAttribute($attr->name,$attr->value);
  460. foreach($source->childNodes as $child)
  461. $this->injectDom($dom,$import,$child);
  462. $target->appendChild($import);
  463. }
  464. /**
  465. * @param string $serviceUrl Web service URL
  466. * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'.
  467. */
  468. protected function buildDOM($serviceUrl,$encoding)
  469. {
  470. $xml="<?xml version=\"1.0\" encoding=\"$encoding\"?>
  471. <definitions name=\"{$this->serviceName}\" targetNamespace=\"{$this->namespace}\"
  472. xmlns=\"http://schemas.xmlsoap.org/wsdl/\"
  473. xmlns:tns=\"{$this->namespace}\"
  474. xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"
  475. xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
  476. xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\"
  477. xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"></definitions>";
  478. $dom=new DOMDocument();
  479. $dom->formatOutput=true;
  480. $dom->loadXml($xml);
  481. $this->addTypes($dom);
  482. $this->addMessages($dom);
  483. $this->addPortTypes($dom);
  484. $this->addBindings($dom);
  485. $this->addService($dom,$serviceUrl);
  486. return $dom;
  487. }
  488. /**
  489. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  490. */
  491. protected function addTypes($dom)
  492. {
  493. if($this->types===array() && $this->elements===array())
  494. return;
  495. $types=$dom->createElement('wsdl:types');
  496. $schema=$dom->createElement('xsd:schema');
  497. $schema->setAttribute('targetNamespace',$this->namespace);
  498. foreach($this->types as $phpType=>$xmlType)
  499. {
  500. if(is_string($xmlType) && strrpos($xmlType,'Array')!==strlen($xmlType)-5)
  501. continue; // simple type
  502. $complexType=$dom->createElement('xsd:complexType');
  503. if(is_string($xmlType))
  504. {
  505. if(($pos=strpos($xmlType,'tns:'))!==false)
  506. $complexType->setAttribute('name',substr($xmlType,4));
  507. else
  508. $complexType->setAttribute('name',$xmlType);
  509. $arrayType = ($dppos=strpos($xmlType,':')) !==false ? substr($xmlType,$dppos + 1) : $xmlType; // strip namespace, if any
  510. $arrayType = substr($arrayType,0,-5); // strip 'Array' from name
  511. if ($this->operationBodyStyle['use'] == self::USE_ENCODED)
  512. {
  513. $complexContent=$dom->createElement('xsd:complexContent');
  514. $restriction=$dom->createElement('xsd:restriction');
  515. $restriction->setAttribute('base','soap-enc:Array');
  516. $attribute=$dom->createElement('xsd:attribute');
  517. $attribute->setAttribute('ref','soap-enc:arrayType');
  518. $attribute->setAttribute('arrayType',(isset(self::$typeMap[$arrayType]) ? 'xsd:' : 'tns:') .$arrayType.'[]');
  519. $restriction->appendChild($attribute);
  520. $complexContent->appendChild($restriction);
  521. $complexType->appendChild($complexContent);
  522. }
  523. else
  524. {
  525. $sequence=$dom->createElement('xsd:sequence');
  526. $element=$dom->createElement('xsd:element');
  527. $element->setAttribute('name','item');
  528. $element->setAttribute('type',(isset(self::$typeMap[$arrayType]) ? self::$typeMap[$arrayType] : 'tns:'.$arrayType));
  529. $element->setAttribute('minOccurs','0');
  530. $element->setAttribute('maxOccurs','unbounded');
  531. $sequence->appendChild($element);
  532. $complexType->appendChild($sequence);
  533. }
  534. }
  535. elseif(is_array($xmlType))
  536. {
  537. $complexType->setAttribute('name',$phpType);
  538. if($xmlType['custom_wsdl']!==false)
  539. {
  540. $custom_dom=new DOMDocument();
  541. $custom_dom->loadXML('<root xmlns:xsd="http://www.w3.org/2001/XMLSchema">'.$xmlType['custom_wsdl'].'</root>');
  542. foreach($custom_dom->documentElement->childNodes as $el)
  543. $this->injectDom($dom,$complexType,$el);
  544. }else{
  545. $all=$dom->createElement('xsd:' . $xmlType['indicator']);
  546. if(!is_null($xmlType['minOccurs']))
  547. $all->setAttribute('minOccurs',$xmlType['minOccurs']);
  548. if(!is_null($xmlType['maxOccurs']))
  549. $all->setAttribute('maxOccurs',$xmlType['maxOccurs']);
  550. if(!is_null($xmlType['nillable']))
  551. $all->setAttribute('nillable',$xmlType['nillable']);
  552. foreach($xmlType['properties'] as $name=>$type)
  553. {
  554. $element=$dom->createElement('xsd:element');
  555. if(!is_null($type[3]))
  556. $element->setAttribute('minOccurs',$type[3]);
  557. if(!is_null($type[4]))
  558. $element->setAttribute('maxOccurs',$type[4]);
  559. if(!is_null($type[2]))
  560. $element->setAttribute('nillable',$type[2]);
  561. $element->setAttribute('name',$name);
  562. $element->setAttribute('type',$type[0]);
  563. $all->appendChild($element);
  564. }
  565. $complexType->appendChild($all);
  566. }
  567. }
  568. $schema->appendChild($complexType);
  569. }
  570. foreach($this->elements as $name=>$parameters)
  571. {
  572. $element=$dom->createElement('xsd:element');
  573. $element->setAttribute('name',$name);
  574. $complexType=$dom->createElement('xsd:complexType');
  575. if (!empty($parameters))
  576. {
  577. $sequence=$dom->createElement('xsd:sequence');
  578. foreach($parameters as $paramName=>$paramOpts)
  579. {
  580. $innerElement=$dom->createElement('xsd:element');
  581. $innerElement->setAttribute('name',$paramName);
  582. $innerElement->setAttribute('type',$paramOpts['type']);
  583. if (isset($paramOpts['nillable']) && $paramOpts['nillable'])
  584. {
  585. $innerElement->setAttribute('nillable','true');
  586. }
  587. $sequence->appendChild($innerElement);
  588. }
  589. $complexType->appendChild($sequence);
  590. }
  591. $element->appendChild($complexType);
  592. $schema->appendChild($element);
  593. }
  594. $types->appendChild($schema);
  595. $dom->documentElement->appendChild($types);
  596. }
  597. /**
  598. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  599. */
  600. protected function addMessages($dom)
  601. {
  602. foreach($this->messages as $name=>$message)
  603. {
  604. $element=$dom->createElement('wsdl:message');
  605. $element->setAttribute('name',$name);
  606. foreach($this->messages[$name] as $partName=>$part)
  607. {
  608. if(is_array($part))
  609. {
  610. $partElement=$dom->createElement('wsdl:part');
  611. $partElement->setAttribute('name',$partName);
  612. if (isset($part['type']))
  613. {
  614. $partElement->setAttribute('type',$part['type']);
  615. }
  616. if (isset($part['element']))
  617. {
  618. $partElement->setAttribute('element',$part['element']);
  619. }
  620. $element->appendChild($partElement);
  621. }
  622. }
  623. $dom->documentElement->appendChild($element);
  624. }
  625. }
  626. /**
  627. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  628. */
  629. protected function addPortTypes($dom)
  630. {
  631. $portType=$dom->createElement('wsdl:portType');
  632. $portType->setAttribute('name',$this->serviceName.'PortType');
  633. $dom->documentElement->appendChild($portType);
  634. foreach($this->operations as $name=>$operation)
  635. $portType->appendChild($this->createPortElement($dom,$name,$operation['doc']));
  636. }
  637. /**
  638. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  639. * @param string $name method name
  640. * @param string $doc doc
  641. */
  642. protected function createPortElement($dom,$name,$doc)
  643. {
  644. $operation=$dom->createElement('wsdl:operation');
  645. $operation->setAttribute('name',$name);
  646. $input=$dom->createElement('wsdl:input');
  647. $input->setAttribute('message', 'tns:'.$name.'In');
  648. $output=$dom->createElement('wsdl:output');
  649. $output->setAttribute('message', 'tns:'.$name.'Out');
  650. $operation->appendChild($dom->createElement('wsdl:documentation',$doc));
  651. $operation->appendChild($input);
  652. $operation->appendChild($output);
  653. return $operation;
  654. }
  655. /**
  656. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  657. */
  658. protected function addBindings($dom)
  659. {
  660. $binding=$dom->createElement('wsdl:binding');
  661. $binding->setAttribute('name',$this->serviceName.'Binding');
  662. $binding->setAttribute('type','tns:'.$this->serviceName.'PortType');
  663. $soapBinding=$dom->createElement('soap:binding');
  664. $soapBinding->setAttribute('style',$this->bindingStyle);
  665. $soapBinding->setAttribute('transport',$this->bindingTransport);
  666. $binding->appendChild($soapBinding);
  667. $dom->documentElement->appendChild($binding);
  668. foreach($this->operations as $name=>$operation)
  669. $binding->appendChild($this->createOperationElement($dom,$name,$operation['headers']));
  670. }
  671. /**
  672. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  673. * @param string $name method name
  674. * @param array $headers array like array('input'=>array(MESSAGE,PART),'output=>array(MESSAGE,PART))
  675. */
  676. protected function createOperationElement($dom,$name,$headers=null)
  677. {
  678. $operation=$dom->createElement('wsdl:operation');
  679. $operation->setAttribute('name', $name);
  680. $soapOperation=$dom->createElement('soap:operation');
  681. $soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name);
  682. if ($this->bindingStyle == self::STYLE_RPC)
  683. {
  684. $soapOperation->setAttribute('style', self::STYLE_RPC);
  685. }
  686. $input=$dom->createElement('wsdl:input');
  687. $output=$dom->createElement('wsdl:output');
  688. $soapBody=$dom->createElement('soap:body');
  689. $operationBodyStyle=$this->operationBodyStyle;
  690. if ($this->bindingStyle == self::STYLE_RPC && !isset($operationBodyStyle['namespace']))
  691. {
  692. $operationBodyStyle['namespace'] = $this->namespace;
  693. }
  694. foreach($operationBodyStyle as $attributeName=>$attributeValue)
  695. {
  696. $soapBody->setAttribute($attributeName, $attributeValue);
  697. }
  698. $input->appendChild($soapBody);
  699. $output->appendChild(clone $soapBody);
  700. if (is_array($headers))
  701. {
  702. if (isset($headers['input']) && is_array($headers['input']) && count($headers['input'])==2)
  703. {
  704. $soapHeader = $dom->createElement('soap:header');
  705. foreach($operationBodyStyle as $attributeName=>$attributeValue) {
  706. $soapHeader->setAttribute($attributeName, $attributeValue);
  707. }
  708. $soapHeader->setAttribute('message', $headers['input'][0]);
  709. $soapHeader->setAttribute('part', $headers['input'][1]);
  710. $input->appendChild($soapHeader);
  711. }
  712. if (isset($headers['output']) && is_array($headers['output']) && count($headers['output'])==2)
  713. {
  714. $soapHeader = $dom->createElement('soap:header');
  715. foreach($operationBodyStyle as $attributeName=>$attributeValue) {
  716. $soapHeader->setAttribute($attributeName, $attributeValue);
  717. }
  718. $soapHeader->setAttribute('message', $headers['output'][0]);
  719. $soapHeader->setAttribute('part', $headers['output'][1]);
  720. $output->appendChild($soapHeader);
  721. }
  722. }
  723. $operation->appendChild($soapOperation);
  724. $operation->appendChild($input);
  725. $operation->appendChild($output);
  726. return $operation;
  727. }
  728. /**
  729. * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
  730. * @param string $serviceUrl Web service URL
  731. */
  732. protected function addService($dom,$serviceUrl)
  733. {
  734. $service=$dom->createElement('wsdl:service');
  735. $service->setAttribute('name', $this->serviceName.'Service');
  736. $port=$dom->createElement('wsdl:port');
  737. $port->setAttribute('name', $this->serviceName.'Port');
  738. $port->setAttribute('binding', 'tns:'.$this->serviceName.'Binding');
  739. $soapAddress=$dom->createElement('soap:address');
  740. $soapAddress->setAttribute('location',$serviceUrl);
  741. $port->appendChild($soapAddress);
  742. $service->appendChild($port);
  743. $dom->documentElement->appendChild($service);
  744. }
  745. /**
  746. * Generate human friendly HTML documentation for complex data types.
  747. * This method can be invoked either by inserting URL parameter "&makedoc" into URL link, e.g. "http://www.mydomain.com/soap/create?makedoc", or simply by calling from another script with argument $return=true.
  748. *
  749. * Each complex data type is described in a separate HTML table containing following columns:
  750. * <ul>
  751. * <li># - attribute ID</li>
  752. * <li>Attribute - attribute name, e.g. firstname</li>
  753. * <li>Type - attribute type, e.g. integer, date, tns:SoapPovCalculationResultArray</li>
  754. * <li>Nill - true|false - whether the attribute is nillable</li>
  755. * <li>Min - minimum number of occurrences</li>
  756. * <li>Max - maximum number of occurrences</li>
  757. * <li>Description - Detailed description of the attribute.</li>
  758. * <li>Example - Attribute example value if provided via PHPDoc property @example.</li>
  759. * </ul>
  760. *
  761. * @param bool $return If true, generated HTML output will be returned rather than directly sent to output buffer
  762. */
  763. public function buildHtmlDocs($return=false)
  764. {
  765. $html='<html><head>';
  766. $html.='<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  767. $html.='<style type="text/css">
  768. table{border-collapse: collapse;background-color: #DDDDDD;}
  769. tr{background-color: #FFFFFF;}
  770. th{background-color: #EEEEEE;}
  771. th, td{font-size: 12px;font-family: courier;padding: 3px;}
  772. </style>';
  773. $html.='</head><body>';
  774. $html.='<h2>WSDL documentation for service '.$this->serviceName.'</h2>';
  775. $html.='<p>Generated on '.date('d.m.Y H:i:s').'</p>';
  776. $html.='<table border="0" cellspacing="1" cellpadding="1">';
  777. $html.='<tr><td>';
  778. if(!empty($this->types))
  779. {
  780. foreach($this->types as $object=>$options){
  781. if(!is_array($options) || empty($options) || !is_array($options['properties']) || empty($options['properties'])){
  782. continue;
  783. }
  784. $params=$options['properties'];
  785. $html.="\n\n<h3>Object: {$object}</h3>";
  786. $html.='<table border="1" cellspacing="1" cellpadding="1">';
  787. $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>';
  788. $c=0;
  789. foreach($params as $param=>$prop){
  790. ++$c;
  791. $html.="\n<tr>"
  792. ."\n\t<td>{$c}</td>"
  793. ."\n\t<td>{$param}</td>"
  794. ."\n\t<td>".(str_replace('xsd:','',$prop[0]))."</td>"
  795. ."\n\t<td>".$prop[2]."</td>"
  796. ."\n\t<td>".($prop[3]==null ? '&nbsp;' : $prop[3])."</td>"
  797. ."\n\t<td>".($prop[4]==null ? '&nbsp;' : $prop[4])."</td>"
  798. ."\n\t<td>{$prop[1]}</td>"
  799. ."\n\t<td>".(trim($prop[5])=='' ? '&nbsp;' : $prop[5])."</td>"
  800. ."\n</tr>";
  801. }
  802. $html.="\n</table><br/>";
  803. }
  804. }
  805. else
  806. $html.='No complex data type found!';
  807. $html.='</td></tr></table></body></html>';
  808. if($return)
  809. return $html;
  810. echo $html;
  811. Yii::app()->end(); // end the app to avoid conflict with text/xml header
  812. }
  813. }