1: <?php
2: /**
3: * CWebService 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: * CWebService encapsulates SoapServer and provides a WSDL-based web service.
13: *
14: * PHP SOAP extension is required.
15: *
16: * CWebService makes use of {@link CWsdlGenerator} and can generate the WSDL
17: * on-the-fly without requiring you to write complex WSDL. However WSDL generator
18: * could be customized through {@link generatorConfig} property.
19: *
20: * To generate the WSDL based on doc comment blocks in the service provider class,
21: * call {@link generateWsdl} or {@link renderWsdl}. To process the web service
22: * requests, call {@link run}.
23: *
24: * @property string $methodName The currently requested method name. Empty if no method is being requested.
25: *
26: * @author Qiang Xue <qiang.xue@gmail.com>
27: * @package system.web.services
28: * @since 1.0
29: */
30: class CWebService extends CComponent
31: {
32: const SOAP_ERROR=1001;
33: /**
34: * @var string|object the web service provider class or object.
35: * If specified as a class name, it can be a path alias.
36: */
37: public $provider;
38: /**
39: * @var string the URL for WSDL. This is required by {@link run()}.
40: */
41: public $wsdlUrl;
42: /**
43: * @var string the URL for the Web service. This is required by {@link generateWsdl()} and {@link renderWsdl()}.
44: */
45: public $serviceUrl;
46: /**
47: * @var integer number of seconds that the generated WSDL can remain valid in cache. Defaults to 0, meaning no caching.
48: */
49: public $wsdlCacheDuration=0;
50: /**
51: * @var string the ID of the cache application component that is used to cache the generated WSDL.
52: * Defaults to 'cache' which refers to the primary cache application component.
53: * Set this property to false if you want to disable caching WSDL.
54: */
55: public $cacheID='cache';
56: /**
57: * @var string encoding of the Web service. Defaults to 'UTF-8'.
58: */
59: public $encoding='UTF-8';
60: /**
61: * @var array a list of classes that are declared as complex types in WSDL.
62: * This should be an array with WSDL types as keys and names of PHP classes as values.
63: * A PHP class can also be specified as a path alias.
64: * @see http://www.php.net/manual/en/soapserver.soapserver.php
65: */
66: public $classMap=array();
67: /**
68: * @var string actor of the SOAP service. Defaults to null, meaning not set.
69: */
70: public $actor;
71: /**
72: * @var string SOAP version (e.g. '1.1' or '1.2'). Defaults to null, meaning not set.
73: */
74: public $soapVersion;
75: /**
76: * @var integer the persistence mode of the SOAP server.
77: * @see http://www.php.net/manual/en/soapserver.setpersistence.php
78: */
79: public $persistence;
80: /**
81: * @var string|array WSDL generator configuration. This property may be useful in purpose of enhancing features
82: * of the standard {@link CWsdlGenerator} class by extending it. For example, some developers may need support
83: * of the <code>xsd:xsd:base64Binary</code> elements. Another use case is to change initial values
84: * at instantiation of the default {@link CWsdlGenerator}. The value of this property will be passed
85: * to {@link Yii::createComponent} to create the generator object. Default value is 'CWsdlGenerator'.
86: * @since 1.1.12
87: */
88: public $generatorConfig='CWsdlGenerator';
89:
90: private $_method;
91:
92:
93: /**
94: * Constructor.
95: * @param mixed $provider the web service provider class name or object
96: * @param string $wsdlUrl the URL for WSDL. This is required by {@link run()}.
97: * @param string $serviceUrl the URL for the Web service. This is required by {@link generateWsdl()} and {@link renderWsdl()}.
98: */
99: public function __construct($provider,$wsdlUrl,$serviceUrl)
100: {
101: $this->provider=$provider;
102: $this->wsdlUrl=$wsdlUrl;
103: $this->serviceUrl=$serviceUrl;
104: }
105:
106: /**
107: * The PHP error handler.
108: * @param CErrorEvent $event the PHP error event
109: */
110: public function handleError($event)
111: {
112: $event->handled=true;
113: $message=$event->message;
114: if(YII_DEBUG)
115: {
116: $trace=debug_backtrace();
117: if(isset($trace[2]) && isset($trace[2]['file']) && isset($trace[2]['line']))
118: $message.=' ('.$trace[2]['file'].':'.$trace[2]['line'].')';
119: }
120: throw new CException($message,self::SOAP_ERROR);
121: }
122:
123: /**
124: * Generates and displays the WSDL as defined by the provider.
125: * @see generateWsdl
126: */
127: public function renderWsdl()
128: {
129: $wsdl=$this->generateWsdl();
130: header('Content-Type: text/xml;charset='.$this->encoding);
131: header('Content-Length: '.(function_exists('mb_strlen') ? mb_strlen($wsdl,'8bit') : strlen($wsdl)));
132: echo $wsdl;
133: }
134:
135: /**
136: * Generates the WSDL as defined by the provider.
137: * The cached version may be used if the WSDL is found valid in cache.
138: * @return string the generated WSDL
139: * @see wsdlCacheDuration
140: */
141: public function generateWsdl()
142: {
143: $providerClass=is_object($this->provider) ? get_class($this->provider) : Yii::import($this->provider,true);
144: if($this->wsdlCacheDuration>0 && $this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null)
145: {
146: $key='Yii.CWebService.'.$providerClass.$this->serviceUrl.$this->encoding;
147: if(($wsdl=$cache->get($key))!==false)
148: return $wsdl;
149: }
150: $generator=Yii::createComponent($this->generatorConfig);
151: $wsdl=$generator->generateWsdl($providerClass,$this->serviceUrl,$this->encoding);
152: if(isset($key))
153: $cache->set($key,$wsdl,$this->wsdlCacheDuration);
154: return $wsdl;
155: }
156:
157: /**
158: * Handles the web service request.
159: */
160: public function run()
161: {
162: header('Content-Type: text/xml;charset='.$this->encoding);
163: if(YII_DEBUG)
164: ini_set("soap.wsdl_cache_enabled",0);
165: $server=new SoapServer($this->wsdlUrl,$this->getOptions());
166: Yii::app()->attachEventHandler('onError',array($this,'handleError'));
167: try
168: {
169: if($this->persistence!==null)
170: $server->setPersistence($this->persistence);
171: if(is_string($this->provider))
172: $provider=Yii::createComponent($this->provider);
173: else
174: $provider=$this->provider;
175:
176: if(method_exists($server,'setObject'))
177: {
178: if (is_array($this->generatorConfig) && isset($this->generatorConfig['bindingStyle'])
179: && $this->generatorConfig['bindingStyle']==='document')
180: {
181: $server->setObject(new CDocumentSoapObjectWrapper($provider));
182: }
183: else
184: {
185: $server->setObject($provider);
186: }
187: }
188: else
189: {
190: if (is_array($this->generatorConfig) && isset($this->generatorConfig['bindingStyle'])
191: && $this->generatorConfig['bindingStyle']==='document')
192: {
193: $server->setClass('CDocumentSoapObjectWrapper',$provider);
194: }
195: else
196: {
197: $server->setClass('CSoapObjectWrapper',$provider);
198: }
199: }
200:
201: if($provider instanceof IWebServiceProvider)
202: {
203: if($provider->beforeWebMethod($this))
204: {
205: $server->handle();
206: $provider->afterWebMethod($this);
207: }
208: }
209: else
210: $server->handle();
211: }
212: catch(Exception $e)
213: {
214: if($e->getCode()!==self::SOAP_ERROR) // non-PHP error
215: {
216: // only log for non-PHP-error case because application's error handler already logs it
217: // php <5.2 doesn't support string conversion auto-magically
218: Yii::log($e->__toString(),CLogger::LEVEL_ERROR,'application');
219: }
220: $message=$e->getMessage();
221: if(YII_DEBUG)
222: $message.=' ('.$e->getFile().':'.$e->getLine().")\n".$e->getTraceAsString();
223:
224: // We need to end application explicitly because of
225: // http://bugs.php.net/bug.php?id=49513
226: Yii::app()->onEndRequest(new CEvent($this));
227: $server->fault(get_class($e),$message);
228: exit(1);
229: }
230: }
231:
232: /**
233: * @return string the currently requested method name. Empty if no method is being requested.
234: */
235: public function getMethodName()
236: {
237: if($this->_method===null)
238: {
239: if(isset($HTTP_RAW_POST_DATA))
240: $request=$HTTP_RAW_POST_DATA;
241: else
242: $request=file_get_contents('php://input');
243: if(preg_match('/<.*?:Body[^>]*>\s*<.*?:(\w+)/mi',$request,$matches))
244: $this->_method=$matches[1];
245: else
246: $this->_method='';
247: }
248: return $this->_method;
249: }
250:
251: /**
252: * @return array options for creating SoapServer instance
253: * @see http://www.php.net/manual/en/soapserver.soapserver.php
254: */
255: protected function getOptions()
256: {
257: $options=array();
258: if($this->soapVersion==='1.1')
259: $options['soap_version']=SOAP_1_1;
260: elseif($this->soapVersion==='1.2')
261: $options['soap_version']=SOAP_1_2;
262: if($this->actor!==null)
263: $options['actor']=$this->actor;
264: $options['encoding']=$this->encoding;
265: foreach($this->classMap as $type=>$className)
266: {
267: $className=Yii::import($className,true);
268: if(is_int($type))
269: $type=$className;
270: $options['classmap'][$type]=$className;
271: }
272: return $options;
273: }
274: }
275:
276:
277: /**
278: * CSoapObjectWrapper is a wrapper class internally used when SoapServer::setObject() is not defined.
279: *
280: * @author Qiang Xue <qiang.xue@gmail.com>
281: * @package system.web.services
282: */
283: class CSoapObjectWrapper
284: {
285: /**
286: * @var object the service provider
287: */
288: public $object=null;
289:
290: /**
291: * Constructor.
292: * @param object $object the service provider
293: */
294: public function __construct($object)
295: {
296: $this->object=$object;
297: }
298:
299: /**
300: * PHP __call magic method.
301: * This method calls the service provider to execute the actual logic.
302: * @param string $name method name
303: * @param array $arguments method arguments
304: * @return mixed method return value
305: */
306: public function __call($name,$arguments)
307: {
308: return call_user_func_array(array($this->object,$name),$arguments);
309: }
310: }
311:
312: /**
313: * CDocumentSoapObjectWrapper is a wrapper class internally used
314: * when generatorConfig contains bindingStyle key set to document value.
315: *
316: * @author Jan Was <jwas@nets.com.pl>
317: * @package system.web.services
318: */
319: class CDocumentSoapObjectWrapper
320: {
321: /**
322: * @var object the service provider
323: */
324: public $object=null;
325:
326: /**
327: * Constructor.
328: * @param object $object the service provider
329: */
330: public function __construct($object)
331: {
332: $this->object=$object;
333: }
334:
335: /**
336: * PHP __call magic method.
337: * This method calls the service provider to execute the actual logic.
338: * @param string $name method name
339: * @param array $arguments method arguments
340: * @return mixed method return value
341: */
342: public function __call($name,$arguments)
343: {
344: if (is_array($arguments) && isset($arguments[0]))
345: {
346: $result = call_user_func_array(array($this->object, $name), (array)$arguments[0]);
347: }
348: else
349: {
350: $result = call_user_func_array(array($this->object, $name), $arguments);
351: }
352: return $result === null ? $result : array($name . 'Result' => $result);
353: }
354: }
355:
356: