CWebService.php 10 KB

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