3 * TSoapService and TSoapServer class file
5 * @author Knut Urdalen <knut.urdalen@gmail.com>
6 * @author Qiang Xue <qiang.xue@gmail.com>
7 * @link http://www.pradosoft.com/
8 * @copyright Copyright © 2005-2014 PradoSoft
9 * @license http://www.pradosoft.com/license/
10 * @package System.Web.Services
16 * TSoapService processes SOAP requests for a PRADO application.
17 * TSoapService requires PHP SOAP extension to be loaded.
19 * TSoapService manages a set of SOAP providers. Each SOAP provider
20 * is a class that implements a set of SOAP methods which are exposed
21 * to SOAP clients for remote invocation. TSoapService generates WSDL
22 * automatically for the SOAP providers by default.
24 * To use TSoapService, configure it in the application specification like following:
27 * <service id="soap" class="System.Web.Services.TSoapService">
28 * <soap id="stockquote" provider="MyStockQuote" />
32 * PHP configuration style:
34 * 'services' => array(
36 * 'class' => 'System.Web.Services.TSoapService'
37 * 'properties' => array(
38 * 'provider' => 'MyStockQuote'
44 * The WSDL for the provider class "MyStockQuote" is generated based on special
45 * comment tags in the class. In particular, if a class method's comment
46 * contains the keyword "@soapmethod", it is considered to be a SOAP method
47 * and will be exposed to SOAP clients. For example,
49 * class MyStockQuote {
51 * * @param string $symbol the stock symbol
52 * * @return float the stock price
55 * public function getQuote($symbol) {...}
59 * With the above SOAP provider, a typical SOAP client may call the method "getQuote"
60 * remotely like the following:
62 * $client=new SoapClient("http://hostname/path/to/index.php?soap=stockquote.wsdl");
63 * echo $client->getQuote("ibm");
66 * Each <soap> element in the application specification actually configures
67 * the properties of a SOAP server which defaults to {@link TSoapServer}.
68 * Therefore, any writable property of {@link TSoapServer} may appear as an attribute
69 * in the <soap> element. For example, the "provider" attribute refers to
70 * the {@link TSoapServer::setProvider Provider} property of {@link TSoapServer}.
71 * The following configuration specifies that the SOAP server is persistent within
72 * the user session (that means a MyStockQuote object will be stored in session)
75 * <service id="soap" class="System.Web.Services.TSoapService">
76 * <soap id="stockquote" provider="MyStockQuote" SessionPersistent="true" />
81 * You may also use your own SOAP server class by specifying the "class" attribute of <soap>.
83 * @author Knut Urdalen <knut.urdalen@gmail.com>
84 * @author Qiang Xue <qiang.xue@gmail.com>
85 * @author Carl G. Mathisen <carlgmathisen@gmail.com>
86 * @package System.Web.Services
89 class TSoapService extends TService
91 const DEFAULT_SOAP_SERVER='TSoapServer';
92 private $_servers=array();
93 private $_configFile=null;
94 private $_wsdlRequest=false;
95 private $_serverID=null;
99 * Sets default service ID to 'soap'.
101 public function __construct()
103 $this->setID('soap');
107 * Initializes this module.
108 * This method is required by the IModule interface.
109 * @param TXmlElement configuration for this module, can be null
110 * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.
112 public function init($config)
114 if($this->_configFile!==null)
116 if(is_file($this->_configFile))
118 $dom=new TXmlDocument;
119 $dom->loadFromFile($this->_configFile);
120 $this->loadConfig($dom);
123 throw new TConfigurationException('soapservice_configfile_invalid',$this->_configFile);
125 $this->loadConfig($config);
127 $this->resolveRequest();
131 * Resolves the request parameter.
132 * It identifies the server ID and whether the request is for WSDL.
133 * @throws THttpException if the server ID cannot be found
135 * @see getIsWsdlRequest
137 protected function resolveRequest()
139 $serverID=$this->getRequest()->getServiceParameter();
140 if(($pos=strrpos($serverID,'.wsdl'))===strlen($serverID)-5)
142 $serverID=substr($serverID,0,$pos);
143 $this->_wsdlRequest=true;
146 $this->_wsdlRequest=false;
147 $this->_serverID=$serverID;
148 if(!isset($this->_servers[$serverID]))
149 throw new THttpException(400,'soapservice_request_invalid',$serverID);
153 * Loads configuration from an XML element
154 * @param mixed configuration node
155 * @throws TConfigurationException if soap server id is not specified or duplicated
157 private function loadConfig($config)
159 if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
161 if(is_array($config))
163 foreach($config['soap'] as $id => $server)
165 $properties = isset($server['properties'])?$server['properties']:array();
166 if(isset($this->_servers[$id]))
167 throw new TConfigurationException('soapservice_serverid_duplicated',$id);
168 $this->_servers[$id]=$properties;
174 foreach($config->getElementsByTagName('soap') as $serverXML)
176 $properties=$serverXML->getAttributes();
177 if(($id=$properties->remove('id'))===null)
178 throw new TConfigurationException('soapservice_serverid_required');
179 if(isset($this->_servers[$id]))
180 throw new TConfigurationException('soapservice_serverid_duplicated',$id);
181 $this->_servers[$id]=$properties;
187 * @return string external configuration file. Defaults to null.
189 public function getConfigFile()
191 return $this->_configFile;
195 * @param string external configuration file in namespace format. The file
196 * must be suffixed with '.xml'.
197 * @throws TInvalidDataValueException if the file is invalid.
199 public function setConfigFile($value)
201 if(($this->_configFile=Prado::getPathOfNamespace($value,Prado::getApplication()->getConfigurationFileExt()))===null)
202 throw new TConfigurationException('soapservice_configfile_invalid',$value);
206 * Constructs a URL with specified page path and GET parameters.
207 * @param string soap server ID
208 * @param array list of GET parameters, null if no GET parameters required
209 * @param boolean whether to encode the ampersand in URL, defaults to true.
210 * @param boolean whether to encode the GET parameters (their names and values), defaults to true.
211 * @return string URL for the page and GET parameters
213 public function constructUrl($serverID,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true)
215 return $this->getRequest()->constructUrl($this->getID(),$serverID,$getParams,$encodeAmpersand,$encodeGetItems);
219 * @return boolean whether this is a request for WSDL
221 public function getIsWsdlRequest()
223 return $this->_wsdlRequest;
227 * @return string the SOAP server ID
229 public function getServerID()
231 return $this->_serverID;
235 * Creates the requested SOAP server.
236 * The SOAP server is initialized with the property values specified
237 * in the configuration.
238 * @return TSoapServer the SOAP server instance
240 protected function createServer()
242 $properties=$this->_servers[$this->_serverID];
244 if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP && isset($config['class']))
245 $serverClass=$config['class'];
246 else if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_XML)
247 $serverClass=$properties->remove('class');
248 if($serverClass===null)
249 $serverClass=self::DEFAULT_SOAP_SERVER;
250 Prado::using($serverClass);
251 $className=($pos=strrpos($serverClass,'.'))!==false?substr($serverClass,$pos+1):$serverClass;
252 if($className!==self::DEFAULT_SOAP_SERVER && !is_subclass_of($className,self::DEFAULT_SOAP_SERVER))
253 throw new TConfigurationException('soapservice_server_invalid',$serverClass);
254 $server=new $className;
255 $server->setID($this->_serverID);
256 foreach($properties as $name=>$value)
257 $server->setSubproperty($name,$value);
263 * If the service parameter ends with '.wsdl', it will serve a WSDL file for
264 * the specified soap server.
265 * Otherwise, it will handle the soap request using the specified server.
267 public function run()
269 Prado::trace("Running SOAP service",'System.Web.Services.TSoapService');
270 $server=$this->createServer();
271 $this->getResponse()->setContentType('text/xml');
272 $this->getResponse()->setCharset($server->getEncoding());
273 if($this->getIsWsdlRequest())
276 Prado::trace("Generating WSDL",'System.Web.Services.TSoapService');
277 $this->getResponse()->clear();
278 $this->getResponse()->write($server->getWsdl());
282 // provide SOAP service
283 Prado::trace("Handling SOAP request",'System.Web.Services.TSoapService');
293 * TSoapServer is a wrapper of the PHP SoapServer class.
294 * It associates a SOAP provider class to the SoapServer object.
295 * It also manages the URI for the SOAP service and WSDL.
297 * @author Qiang Xue <qiang.xue@gmail.com>
298 * @package System.Web.Services
301 class TSoapServer extends TApplicationComponent
303 const WSDL_CACHE_PREFIX='wsdl.';
308 private $_version='';
310 private $_encoding='';
313 private $_persistent=false;
314 private $_wsdlUri='';
316 private $_requestedMethod;
321 * @return string the ID of the SOAP server
323 public function getID()
329 * @param string the ID of the SOAP server
330 * @throws TInvalidDataValueException if the ID ends with '.wsdl'.
332 public function setID($id)
334 if(strrpos($this->_id,'.wsdl')===strlen($this->_id)-5)
335 throw new TInvalidDataValueException('soapserver_id_invalid',$id);
340 * Handles the SOAP request.
342 public function run()
344 if(($provider=$this->getProvider())!==null)
346 Prado::using($provider);
347 $providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
348 $this->guessMethodCallRequested($providerClass);
349 $server=$this->createServer();
350 $server->setClass($providerClass, $this);
351 if($this->_persistent)
352 $server->setPersistence(SOAP_PERSISTENCE_SESSION);
355 $server=$this->createServer();
362 if($this->getApplication()->getMode()===TApplicationMode::Debug)
363 $this->fault($e->getMessage(), $e->__toString());
365 $this->fault($e->getMessage());
370 * Generate a SOAP fault message.
371 * @param string message title
372 * @param mixed message details
373 * @param string message code, defalt is 'SERVER'.
374 * @param string actors
375 * @param string message name
377 public function fault($title, $details='', $code='SERVER', $actor='', $name='')
379 Prado::trace('SOAP-Fault '.$code. ' '.$title.' : '.$details, 'System.Web.Services.TSoapService');
380 $this->_server->fault($code, $title, $actor, $details, $name);
384 * Guess the SOAP method request from the actual SOAP message
386 * @param string $class current handler class.
388 protected function guessMethodCallRequested($class)
390 $namespace = $class.'wsdl';
391 $message = file_get_contents("php://input");
393 if(preg_match('/xmlns:([^=]+)="urn:'.$namespace.'"/', $message, $matches))
395 if(preg_match('/<'.$matches[1].':([a-zA-Z_]+[a-zA-Z0-9_]+)/', $message, $method))
397 $this->_requestedMethod = $method[1];
403 * Soap method guessed from the SOAP message received.
404 * @return string soap method request, null if not found.
406 public function getRequestedMethod()
408 return $this->_requestedMethod;
412 * Creates the SoapServer instance.
415 protected function createServer()
417 if($this->_server===null)
419 if($this->getApplication()->getMode()===TApplicationMode::Debug)
420 ini_set("soap.wsdl_cache_enabled",0);
421 $this->_server = new SoapServer($this->getWsdlUri(),$this->getOptions());
423 return $this->_server;
427 * @return array options for creating SoapServer instance
429 protected function getOptions()
432 if($this->_version==='1.1')
433 $options['soap_version']=SOAP_1_1;
434 else if($this->_version==='1.2')
435 $options['soap_version']=SOAP_1_2;
436 if(!empty($this->_actor))
437 $options['actor']=$this->_actor;
438 if(!empty($this->_encoding))
439 $options['encoding']=$this->_encoding;
440 if(!empty($this->_uri))
441 $options['uri']=$this->_uri;
442 if(is_string($this->_classMap))
444 foreach(preg_split('/\s*,\s*/', $this->_classMap) as $className)
445 $options['classmap'][$className]=$className; //complex type uses the class name in the wsdl
451 * Returns the WSDL content of the SOAP server.
452 * If {@link getWsdlUri WsdlUri} is set, its content will be returned.
453 * If not, the {@link setProvider Provider} class will be investigated
454 * and the WSDL will be automatically genearted.
455 * @return string the WSDL content of the SOAP server
457 public function getWsdl()
459 if($this->_wsdlUri==='')
461 $provider=$this->getProvider();
462 $providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
463 Prado::using($provider);
464 if($this->getApplication()->getMode()===TApplicationMode::Performance && ($cache=$this->getApplication()->getCache())!==null)
466 $wsdl=$cache->get(self::WSDL_CACHE_PREFIX.$providerClass);
469 Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
470 $wsdl=WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
471 $cache->set(self::WSDL_CACHE_PREFIX.$providerClass,$wsdl);
476 Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
477 return WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
481 return file_get_contents($this->_wsdlUri);
485 * @return string the URI for WSDL
487 public function getWsdlUri()
489 if($this->_wsdlUri==='')
490 return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID().'.wsdl',false);
492 return $this->_wsdlUri;
496 * @param string the URI for WSDL
498 public function setWsdlUri($value)
500 $this->_wsdlUri=$value;
504 * @return string the URI for the SOAP service
506 public function getUri()
509 return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID(),false);
515 * @param string the URI for the SOAP service
517 public function setUri($uri)
523 * @return string the SOAP provider class (in namespace format)
525 public function getProvider()
527 return $this->_provider;
531 * @param string the SOAP provider class (in namespace format)
533 public function setProvider($provider)
535 $this->_provider=$provider;
539 * @return string SOAP version, defaults to empty (meaning not set).
541 public function getVersion()
543 return $this->_version;
547 * @param string SOAP version, either '1.1' or '1.2'
548 * @throws TInvalidDataValueException if neither '1.1' nor '1.2'
550 public function setVersion($value)
552 if($value==='1.1' || $value==='1.2' || $value==='')
553 $this->_version=$value;
555 throw new TInvalidDataValueException('soapserver_version_invalid',$value);
559 * @return string actor of the SOAP service
561 public function getActor()
563 return $this->_actor;
567 * @param string actor of the SOAP service
569 public function setActor($value)
571 $this->_actor=$value;
575 * @return string encoding of the SOAP service
577 public function getEncoding()
579 return $this->_encoding;
583 * @param string encoding of the SOAP service
585 public function setEncoding($value)
587 $this->_encoding=$value;
591 * @return boolean whether the SOAP service is persistent within session. Defaults to false.
593 public function getSessionPersistent()
595 return $this->_persistent;
599 * @param boolean whether the SOAP service is persistent within session.
601 public function setSessionPersistent($value)
603 $this->_persistent=TPropertyValue::ensureBoolean($value);
607 * @return string comma delimit list of complex type classes.
609 public function getClassMaps()
611 return $this->_classMap;
615 * @return string comma delimit list of class names
617 public function setClassMaps($classes)
619 $this->_classMap = $classes;