Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • Net
  • None
  • PHP
  • system
    • base
    • caching
      • dependencies
    • collections
    • console
    • db
      • ar
      • schema
        • cubrid
        • mssql
        • mysql
        • oci
        • pgsql
        • sqlite
    • i18n
      • gettext
    • logging
    • test
    • utils
    • validators
    • web
      • actions
      • auth
      • filters
      • form
      • helpers
      • renderers
      • services
      • widgets
        • captcha
        • pagers
  • Text
    • Highlighter
  • zii
    • behaviors
    • widgets
      • grid
      • jui

Classes

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