~horux-dev/horux-webcli/thfo

« back to all changes in this revision

Viewing changes to yii/framework/web/services/CWsdlGenerator.php

  • Committer: Thierry Forchelet
  • Date: 2011-02-25 13:30:15 UTC
  • Revision ID: thierry.forchelet@letux.ch-20110225133015-zxyj9w7sqv8ly971
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * CWsdlGenerator class file.
 
4
 *
 
5
 * @author Qiang Xue <qiang.xue@gmail.com>
 
6
 * @link http://www.yiiframework.com/
 
7
 * @copyright Copyright &copy; 2008-2011 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
 
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
 * @author Qiang Xue <qiang.xue@gmail.com>
 
79
 * @version $Id: CWsdlGenerator.php 2799 2011-01-01 19:31:13Z qiang.xue $
 
80
 * @package system.web.services
 
81
 * @since 1.0
 
82
 */
 
83
class CWsdlGenerator extends CComponent
 
84
{
 
85
        /**
 
86
         * @var string the namespace to be used in the generated WSDL.
 
87
         * If not set, it defaults to the name of the class that WSDL is generated upon.
 
88
         */
 
89
        public $namespace;
 
90
        /**
 
91
         * @var string the name of the generated WSDL.
 
92
         * If not set, it defaults to "urn:{$className}wsdl".
 
93
         */
 
94
        public $serviceName;
 
95
 
 
96
        private $_operations;
 
97
        private $_types;
 
98
        private $_messages;
 
99
 
 
100
        /**
 
101
         * Generates the WSDL for the given class.
 
102
         * @param string $className class name
 
103
         * @param string $serviceUrl Web service URL
 
104
         * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'.
 
105
         * @return string the generated WSDL
 
106
         */
 
107
        public function generateWsdl($className, $serviceUrl, $encoding='UTF-8')
 
108
        {
 
109
                $this->_operations=array();
 
110
                $this->_types=array();
 
111
                $this->_messages=array();
 
112
                if($this->serviceName===null)
 
113
                        $this->serviceName=$className;
 
114
                if($this->namespace===null)
 
115
                        $this->namespace="urn:{$className}wsdl";
 
116
 
 
117
                $reflection=new ReflectionClass($className);
 
118
                foreach($reflection->getMethods() as $method)
 
119
                {
 
120
                        if($method->isPublic())
 
121
                                $this->processMethod($method);
 
122
                }
 
123
 
 
124
                return $this->buildDOM($serviceUrl,$encoding)->saveXML();
 
125
        }
 
126
 
 
127
        /*
 
128
         * @param ReflectionMethod $method method
 
129
         */
 
130
        private function processMethod($method)
 
131
        {
 
132
                $comment=$method->getDocComment();
 
133
                if(strpos($comment,'@soap')===false)
 
134
                        return;
 
135
 
 
136
                $methodName=$method->getName();
 
137
                $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
 
138
                $params=$method->getParameters();
 
139
                $message=array();
 
140
                $n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
 
141
                if($n>count($params))
 
142
                        $n=count($params);
 
143
                for($i=0;$i<$n;++$i)
 
144
                        $message[$params[$i]->getName()]=array($this->processType($matches[1][$i]), trim($matches[3][$i])); // name => type, doc
 
145
 
 
146
                $this->_messages[$methodName.'Request']=$message;
 
147
 
 
148
                if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
 
149
                        $return=array($this->processType($matches[1]),trim($matches[2])); // type, doc
 
150
                else
 
151
                        $return=null;
 
152
                $this->_messages[$methodName.'Response']=array('return'=>$return);
 
153
 
 
154
                if(preg_match('/^\/\*+\s*([^@]*?)\n@/s',$comment,$matches))
 
155
                        $doc=trim($matches[1]);
 
156
                else
 
157
                        $doc='';
 
158
                $this->_operations[$methodName]=$doc;
 
159
        }
 
160
 
 
161
        /*
 
162
         * @param string $type PHP variable type
 
163
         */
 
164
        private function processType($type)
 
165
        {
 
166
                static $typeMap=array(
 
167
                        'string'=>'xsd:string',
 
168
                        'str'=>'xsd:string',
 
169
                        'int'=>'xsd:int',
 
170
                        'integer'=>'xsd:integer',
 
171
                        'float'=>'xsd:float',
 
172
                        'double'=>'xsd:float',
 
173
                        'bool'=>'xsd:boolean',
 
174
                        'boolean'=>'xsd:boolean',
 
175
                        'date'=>'xsd:date',
 
176
                        'time'=>'xsd:time',
 
177
                        'datetime'=>'xsd:dateTime',
 
178
                        'array'=>'soap-enc:Array',
 
179
                        'object'=>'xsd:struct',
 
180
                        'mixed'=>'xsd:anyType',
 
181
                );
 
182
                if(isset($typeMap[$type]))
 
183
                        return $typeMap[$type];
 
184
                else if(isset($this->_types[$type]))
 
185
                        return is_array($this->_types[$type]) ? 'tns:'.$type : $this->_types[$type];
 
186
                else if(($pos=strpos($type,'[]'))!==false) // if it is an array
 
187
                {
 
188
                        $type=substr($type,0,$pos);
 
189
                        if(isset($typeMap[$type]))
 
190
                                $this->_types[$type.'[]']='xsd:'.$type.'Array';
 
191
                        else
 
192
                        {
 
193
                                $this->_types[$type.'[]']='tns:'.$type.'Array';
 
194
                                $this->processType($type);
 
195
                        }
 
196
                        return $this->_types[$type.'[]'];
 
197
                }
 
198
                else // class type
 
199
                {
 
200
                        $type=Yii::import($type,true);
 
201
                        $this->_types[$type]=array();
 
202
                        $class=new ReflectionClass($type);
 
203
                        foreach($class->getProperties() as $property)
 
204
                        {
 
205
                                $comment=$property->getDocComment();
 
206
                                if($property->isPublic() && strpos($comment,'@soap')!==false)
 
207
                                {
 
208
                                        if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/mi',$comment,$matches))
 
209
                                                $this->_types[$type][$property->getName()]=array($this->processType($matches[1]),trim($matches[3]));  // name => type, doc
 
210
                                }
 
211
                        }
 
212
                        return 'tns:'.$type;
 
213
                }
 
214
        }
 
215
 
 
216
        /*
 
217
         * @param string $serviceUrl Web service URL
 
218
         * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'.
 
219
         */
 
220
        private function buildDOM($serviceUrl,$encoding)
 
221
        {
 
222
                $xml="<?xml version=\"1.0\" encoding=\"$encoding\"?>
 
223
<definitions name=\"{$this->serviceName}\" targetNamespace=\"{$this->namespace}\"
 
224
     xmlns=\"http://schemas.xmlsoap.org/wsdl/\"
 
225
     xmlns:tns=\"{$this->namespace}\"
 
226
     xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"
 
227
     xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
 
228
         xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\"
 
229
     xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"></definitions>";
 
230
 
 
231
                $dom=new DOMDocument();
 
232
                $dom->loadXml($xml);
 
233
                $this->addTypes($dom);
 
234
 
 
235
                $this->addMessages($dom);
 
236
                $this->addPortTypes($dom);
 
237
                $this->addBindings($dom);
 
238
                $this->addService($dom,$serviceUrl);
 
239
 
 
240
                return $dom;
 
241
        }
 
242
 
 
243
        /*
 
244
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
245
         */
 
246
        private function addTypes($dom)
 
247
        {
 
248
                if($this->_types===array())
 
249
                        return;
 
250
                $types=$dom->createElement('wsdl:types');
 
251
                $schema=$dom->createElement('xsd:schema');
 
252
                $schema->setAttribute('targetNamespace',$this->namespace);
 
253
                foreach($this->_types as $phpType=>$xmlType)
 
254
                {
 
255
                        if(is_string($xmlType) && strrpos($xmlType,'Array')!==strlen($xmlType)-5)
 
256
                                continue;  // simple type
 
257
                        $complexType=$dom->createElement('xsd:complexType');
 
258
                        if(is_string($xmlType))
 
259
                        {
 
260
                                if(($pos=strpos($xmlType,'tns:'))!==false)
 
261
                                        $complexType->setAttribute('name',substr($xmlType,4));
 
262
                                else
 
263
                                        $complexType->setAttribute('name',$xmlType);
 
264
                                $complexContent=$dom->createElement('xsd:complexContent');
 
265
                                $restriction=$dom->createElement('xsd:restriction');
 
266
                                $restriction->setAttribute('base','soap-enc:Array');
 
267
                                $attribute=$dom->createElement('xsd:attribute');
 
268
                                $attribute->setAttribute('ref','soap-enc:arrayType');
 
269
                                $attribute->setAttribute('wsdl:arrayType',substr($xmlType,0,strlen($xmlType)-5).'[]');
 
270
                                $restriction->appendChild($attribute);
 
271
                                $complexContent->appendChild($restriction);
 
272
                                $complexType->appendChild($complexContent);
 
273
                        }
 
274
                        else if(is_array($xmlType))
 
275
                        {
 
276
                                $complexType->setAttribute('name',$phpType);
 
277
                                $all=$dom->createElement('xsd:all');
 
278
                                foreach($xmlType as $name=>$type)
 
279
                                {
 
280
                                        $element=$dom->createElement('xsd:element');
 
281
                                        $element->setAttribute('name',$name);
 
282
                                        $element->setAttribute('type',$type[0]);
 
283
                                        $all->appendChild($element);
 
284
                                }
 
285
                                $complexType->appendChild($all);
 
286
                        }
 
287
                        $schema->appendChild($complexType);
 
288
                        $types->appendChild($schema);
 
289
                }
 
290
 
 
291
                $dom->documentElement->appendChild($types);
 
292
        }
 
293
 
 
294
        /*
 
295
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
296
         */
 
297
        private function addMessages($dom)
 
298
        {
 
299
                foreach($this->_messages as $name=>$message)
 
300
                {
 
301
                        $element=$dom->createElement('wsdl:message');
 
302
                        $element->setAttribute('name',$name);
 
303
                        foreach($this->_messages[$name] as $partName=>$part)
 
304
                        {
 
305
                                if(is_array($part))
 
306
                                {
 
307
                                        $partElement=$dom->createElement('wsdl:part');
 
308
                                        $partElement->setAttribute('name',$partName);
 
309
                                        $partElement->setAttribute('type',$part[0]);
 
310
                                        $element->appendChild($partElement);
 
311
                                }
 
312
                        }
 
313
                        $dom->documentElement->appendChild($element);
 
314
                }
 
315
        }
 
316
 
 
317
        /*
 
318
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
319
         */
 
320
        private function addPortTypes($dom)
 
321
        {
 
322
                $portType=$dom->createElement('wsdl:portType');
 
323
                $portType->setAttribute('name',$this->serviceName.'PortType');
 
324
                $dom->documentElement->appendChild($portType);
 
325
                foreach($this->_operations as $name=>$doc)
 
326
                        $portType->appendChild($this->createPortElement($dom,$name,$doc));
 
327
        }
 
328
 
 
329
        /*
 
330
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
331
         * @param string $name method name
 
332
         * @param string $doc doc
 
333
         */
 
334
        private function createPortElement($dom,$name,$doc)
 
335
        {
 
336
                $operation=$dom->createElement('wsdl:operation');
 
337
                $operation->setAttribute('name',$name);
 
338
 
 
339
                $input = $dom->createElement('wsdl:input');
 
340
                $input->setAttribute('message', 'tns:'.$name.'Request');
 
341
                $output = $dom->createElement('wsdl:output');
 
342
                $output->setAttribute('message', 'tns:'.$name.'Response');
 
343
 
 
344
                $operation->appendChild($dom->createElement('wsdl:documentation',$doc));
 
345
                $operation->appendChild($input);
 
346
                $operation->appendChild($output);
 
347
 
 
348
                return $operation;
 
349
        }
 
350
 
 
351
        /*
 
352
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
353
         */
 
354
        private function addBindings($dom)
 
355
        {
 
356
                $binding=$dom->createElement('wsdl:binding');
 
357
                $binding->setAttribute('name',$this->serviceName.'Binding');
 
358
                $binding->setAttribute('type','tns:'.$this->serviceName.'PortType');
 
359
 
 
360
                $soapBinding=$dom->createElement('soap:binding');
 
361
                $soapBinding->setAttribute('style','rpc');
 
362
                $soapBinding->setAttribute('transport','http://schemas.xmlsoap.org/soap/http');
 
363
                $binding->appendChild($soapBinding);
 
364
 
 
365
                $dom->documentElement->appendChild($binding);
 
366
 
 
367
                foreach($this->_operations as $name=>$doc)
 
368
                        $binding->appendChild($this->createOperationElement($dom,$name));
 
369
        }
 
370
 
 
371
        /*
 
372
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
373
         * @param string $name method name
 
374
         */
 
375
        private function createOperationElement($dom,$name)
 
376
        {
 
377
                $operation=$dom->createElement('wsdl:operation');
 
378
                $operation->setAttribute('name', $name);
 
379
                $soapOperation = $dom->createElement('soap:operation');
 
380
                $soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name);
 
381
                $soapOperation->setAttribute('style','rpc');
 
382
 
 
383
                $input = $dom->createElement('wsdl:input');
 
384
                $output = $dom->createElement('wsdl:output');
 
385
 
 
386
                $soapBody = $dom->createElement('soap:body');
 
387
                $soapBody->setAttribute('use', 'encoded');
 
388
                $soapBody->setAttribute('namespace', $this->namespace);
 
389
                $soapBody->setAttribute('encodingStyle', 'http://schemas.xmlsoap.org/soap/encoding/');
 
390
                $input->appendChild($soapBody);
 
391
                $output->appendChild(clone $soapBody);
 
392
 
 
393
                $operation->appendChild($soapOperation);
 
394
                $operation->appendChild($input);
 
395
                $operation->appendChild($output);
 
396
 
 
397
                return $operation;
 
398
        }
 
399
 
 
400
        /*
 
401
         * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree
 
402
         * @param string $serviceUrl Web service URL
 
403
         */
 
404
        private function addService($dom,$serviceUrl)
 
405
        {
 
406
                $service=$dom->createElement('wsdl:service');
 
407
                $service->setAttribute('name', $this->serviceName.'Service');
 
408
 
 
409
                $port=$dom->createElement('wsdl:port');
 
410
                $port->setAttribute('name', $this->serviceName.'Port');
 
411
                $port->setAttribute('binding', 'tns:'.$this->serviceName.'Binding');
 
412
 
 
413
                $soapAddress=$dom->createElement('soap:address');
 
414
                $soapAddress->setAttribute('location',$serviceUrl);
 
415
                $port->appendChild($soapAddress);
 
416
                $service->appendChild($port);
 
417
                $dom->documentElement->appendChild($service);
 
418
        }
 
419
}