diff options
author | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
---|---|---|
committer | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
commit | 6f7fdef0f500cd4bb540affd3bc1482243f337c1 (patch) | |
tree | 4853eecd0769a903e6130c1896e1d070848150dd /lib/prado/framework/Web/Services/TSoapService.php | |
parent | 61f2ea48a4e11cb5fb941b3783e19c9e9ef38a45 (diff) |
* Prado 3.3.0
Diffstat (limited to 'lib/prado/framework/Web/Services/TSoapService.php')
-rw-r--r-- | lib/prado/framework/Web/Services/TSoapService.php | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/lib/prado/framework/Web/Services/TSoapService.php b/lib/prado/framework/Web/Services/TSoapService.php new file mode 100644 index 0000000..9554af2 --- /dev/null +++ b/lib/prado/framework/Web/Services/TSoapService.php @@ -0,0 +1,622 @@ +<?php +/** + * TSoapService and TSoapServer class file + * + * @author Knut Urdalen <knut.urdalen@gmail.com> + * @author Qiang Xue <qiang.xue@gmail.com> + * @link https://github.com/pradosoft/prado + * @copyright Copyright © 2005-2015 The PRADO Group + * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT + * @package System.Web.Services + */ + +/** + * TSoapService class + * + * TSoapService processes SOAP requests for a PRADO application. + * TSoapService requires PHP SOAP extension to be loaded. + * + * TSoapService manages a set of SOAP providers. Each SOAP provider + * is a class that implements a set of SOAP methods which are exposed + * to SOAP clients for remote invocation. TSoapService generates WSDL + * automatically for the SOAP providers by default. + * + * To use TSoapService, configure it in the application specification like following: + * <code> + * <services> + * <service id="soap" class="System.Web.Services.TSoapService"> + * <soap id="stockquote" provider="MyStockQuote" /> + * </service> + * </services> + * </code> + * PHP configuration style: + * <code> + * 'services' => array( + * 'soap' => array( + * 'class' => 'System.Web.Services.TSoapService' + * 'properties' => array( + * 'provider' => 'MyStockQuote' + * ) + * ) + * ) + * </code> + * + * The WSDL for the provider class "MyStockQuote" is generated based on special + * comment tags in the class. In particular, if a class method's comment + * contains the keyword "@soapmethod", it is considered to be a SOAP method + * and will be exposed to SOAP clients. For example, + * <code> + * class MyStockQuote { + * / ** + * * @param string $symbol the stock symbol + * * @return float the stock price + * * @soapmethod + * * / + * public function getQuote($symbol) {...} + * } + * </code> + * + * With the above SOAP provider, a typical SOAP client may call the method "getQuote" + * remotely like the following: + * <code> + * $client=new SoapClient("http://hostname/path/to/index.php?soap=stockquote.wsdl"); + * echo $client->getQuote("ibm"); + * </code> + * + * Each <soap> element in the application specification actually configures + * the properties of a SOAP server which defaults to {@link TSoapServer}. + * Therefore, any writable property of {@link TSoapServer} may appear as an attribute + * in the <soap> element. For example, the "provider" attribute refers to + * the {@link TSoapServer::setProvider Provider} property of {@link TSoapServer}. + * The following configuration specifies that the SOAP server is persistent within + * the user session (that means a MyStockQuote object will be stored in session) + * <code> + * <services> + * <service id="soap" class="System.Web.Services.TSoapService"> + * <soap id="stockquote" provider="MyStockQuote" SessionPersistent="true" /> + * </service> + * </services> + * </code> + * + * You may also use your own SOAP server class by specifying the "class" attribute of <soap>. + * + * @author Knut Urdalen <knut.urdalen@gmail.com> + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carl G. Mathisen <carlgmathisen@gmail.com> + * @package System.Web.Services + * @since 3.1 + */ +class TSoapService extends TService +{ + const DEFAULT_SOAP_SERVER='TSoapServer'; + private $_servers=array(); + private $_configFile=null; + private $_wsdlRequest=false; + private $_serverID=null; + + /** + * Constructor. + * Sets default service ID to 'soap'. + */ + public function __construct() + { + $this->setID('soap'); + } + + /** + * Initializes this module. + * This method is required by the IModule interface. + * @param TXmlElement configuration for this module, can be null + * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid. + */ + public function init($config) + { + if($this->_configFile!==null) + { + if(is_file($this->_configFile)) + { + $dom=new TXmlDocument; + $dom->loadFromFile($this->_configFile); + $this->loadConfig($dom); + } + else + throw new TConfigurationException('soapservice_configfile_invalid',$this->_configFile); + } + $this->loadConfig($config); + + $this->resolveRequest(); + } + + /** + * Resolves the request parameter. + * It identifies the server ID and whether the request is for WSDL. + * @throws THttpException if the server ID cannot be found + * @see getServerID + * @see getIsWsdlRequest + */ + protected function resolveRequest() + { + $serverID=$this->getRequest()->getServiceParameter(); + if(($pos=strrpos($serverID,'.wsdl'))===strlen($serverID)-5) + { + $serverID=substr($serverID,0,$pos); + $this->_wsdlRequest=true; + } + else + $this->_wsdlRequest=false; + $this->_serverID=$serverID; + if(!isset($this->_servers[$serverID])) + throw new THttpException(400,'soapservice_request_invalid',$serverID); + } + + /** + * Loads configuration from an XML element + * @param mixed configuration node + * @throws TConfigurationException if soap server id is not specified or duplicated + */ + private function loadConfig($config) + { + if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP) + { + if(is_array($config)) + { + foreach($config['soap'] as $id => $server) + { + $properties = isset($server['properties'])?$server['properties']:array(); + if(isset($this->_servers[$id])) + throw new TConfigurationException('soapservice_serverid_duplicated',$id); + $this->_servers[$id]=$properties; + } + } + } + else + { + foreach($config->getElementsByTagName('soap') as $serverXML) + { + $properties=$serverXML->getAttributes(); + if(($id=$properties->remove('id'))===null) + throw new TConfigurationException('soapservice_serverid_required'); + if(isset($this->_servers[$id])) + throw new TConfigurationException('soapservice_serverid_duplicated',$id); + $this->_servers[$id]=$properties; + } + } + } + + /** + * @return string external configuration file. Defaults to null. + */ + public function getConfigFile() + { + return $this->_configFile; + } + + /** + * @param string external configuration file in namespace format. The file + * must be suffixed with '.xml'. + * @throws TInvalidDataValueException if the file is invalid. + */ + public function setConfigFile($value) + { + if(($this->_configFile=Prado::getPathOfNamespace($value,Prado::getApplication()->getConfigurationFileExt()))===null) + throw new TConfigurationException('soapservice_configfile_invalid',$value); + } + + /** + * Constructs a URL with specified page path and GET parameters. + * @param string soap server ID + * @param array list of GET parameters, null if no GET parameters required + * @param boolean whether to encode the ampersand in URL, defaults to true. + * @param boolean whether to encode the GET parameters (their names and values), defaults to true. + * @return string URL for the page and GET parameters + */ + public function constructUrl($serverID,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true) + { + return $this->getRequest()->constructUrl($this->getID(),$serverID,$getParams,$encodeAmpersand,$encodeGetItems); + } + + /** + * @return boolean whether this is a request for WSDL + */ + public function getIsWsdlRequest() + { + return $this->_wsdlRequest; + } + + /** + * @return string the SOAP server ID + */ + public function getServerID() + { + return $this->_serverID; + } + + /** + * Creates the requested SOAP server. + * The SOAP server is initialized with the property values specified + * in the configuration. + * @return TSoapServer the SOAP server instance + */ + protected function createServer() + { + $properties=$this->_servers[$this->_serverID]; + $serverClass=null; + if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP && isset($config['class'])) + $serverClass=$config['class']; + else if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_XML) + $serverClass=$properties->remove('class'); + if($serverClass===null) + $serverClass=self::DEFAULT_SOAP_SERVER; + Prado::using($serverClass); + $className=($pos=strrpos($serverClass,'.'))!==false?substr($serverClass,$pos+1):$serverClass; + if($className!==self::DEFAULT_SOAP_SERVER && !is_subclass_of($className,self::DEFAULT_SOAP_SERVER)) + throw new TConfigurationException('soapservice_server_invalid',$serverClass); + $server=new $className; + $server->setID($this->_serverID); + foreach($properties as $name=>$value) + $server->setSubproperty($name,$value); + return $server; + } + + /** + * Runs the service. + * If the service parameter ends with '.wsdl', it will serve a WSDL file for + * the specified soap server. + * Otherwise, it will handle the soap request using the specified server. + */ + public function run() + { + Prado::trace("Running SOAP service",'System.Web.Services.TSoapService'); + $server=$this->createServer(); + $this->getResponse()->setContentType('text/xml'); + $this->getResponse()->setCharset($server->getEncoding()); + if($this->getIsWsdlRequest()) + { + // server WSDL file + Prado::trace("Generating WSDL",'System.Web.Services.TSoapService'); + $this->getResponse()->clear(); + $this->getResponse()->write($server->getWsdl()); + } + else + { + // provide SOAP service + Prado::trace("Handling SOAP request",'System.Web.Services.TSoapService'); + $server->run(); + } + } +} + + +/** + * TSoapServer class. + * + * TSoapServer is a wrapper of the PHP SoapServer class. + * It associates a SOAP provider class to the SoapServer object. + * It also manages the URI for the SOAP service and WSDL. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @package System.Web.Services + * @since 3.1 + */ +class TSoapServer extends TApplicationComponent +{ + const WSDL_CACHE_PREFIX='wsdl.'; + + private $_id; + private $_provider; + + private $_version=''; + private $_actor=''; + private $_encoding=''; + private $_uri=''; + private $_classMap; + private $_persistent=false; + private $_wsdlUri=''; + + private $_requestedMethod; + + private $_server; + + /** + * @return string the ID of the SOAP server + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string the ID of the SOAP server + * @throws TInvalidDataValueException if the ID ends with '.wsdl'. + */ + public function setID($id) + { + if(strrpos($this->_id,'.wsdl')===strlen($this->_id)-5) + throw new TInvalidDataValueException('soapserver_id_invalid',$id); + $this->_id=$id; + } + + /** + * Handles the SOAP request. + */ + public function run() + { + if(($provider=$this->getProvider())!==null) + { + Prado::using($provider); + $providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider; + $this->guessMethodCallRequested($providerClass); + $server=$this->createServer(); + $server->setClass($providerClass, $this); + if($this->_persistent) + $server->setPersistence(SOAP_PERSISTENCE_SESSION); + } + else + $server=$this->createServer(); + try + { + $server->handle(); + } + catch (Exception $e) + { + if($this->getApplication()->getMode()===TApplicationMode::Debug) + $this->fault($e->getMessage(), $e->__toString()); + else + $this->fault($e->getMessage()); + } + } + + /** + * Generate a SOAP fault message. + * @param string message title + * @param mixed message details + * @param string message code, defalt is 'SERVER'. + * @param string actors + * @param string message name + */ + public function fault($title, $details='', $code='SERVER', $actor='', $name='') + { + Prado::trace('SOAP-Fault '.$code. ' '.$title.' : '.$details, 'System.Web.Services.TSoapService'); + $this->_server->fault($code, $title, $actor, $details, $name); + } + + /** + * Guess the SOAP method request from the actual SOAP message + * + * @param string $class current handler class. + */ + protected function guessMethodCallRequested($class) + { + $namespace = $class.'wsdl'; + $message = file_get_contents("php://input"); + $matches= array(); + if(preg_match('/xmlns:([^=]+)="urn:'.$namespace.'"/', $message, $matches)) + { + if(preg_match('/<'.$matches[1].':([a-zA-Z_]+[a-zA-Z0-9_]+)/', $message, $method)) + { + $this->_requestedMethod = $method[1]; + } + } + } + + /** + * Soap method guessed from the SOAP message received. + * @return string soap method request, null if not found. + */ + public function getRequestedMethod() + { + return $this->_requestedMethod; + } + + /** + * Creates the SoapServer instance. + * @return SoapServer + */ + protected function createServer() + { + if($this->_server===null) + { + if($this->getApplication()->getMode()===TApplicationMode::Debug) + ini_set("soap.wsdl_cache_enabled",0); + $this->_server = new SoapServer($this->getWsdlUri(),$this->getOptions()); + } + return $this->_server; + } + + /** + * @return array options for creating SoapServer instance + */ + protected function getOptions() + { + $options=array(); + if($this->_version==='1.1') + $options['soap_version']=SOAP_1_1; + else if($this->_version==='1.2') + $options['soap_version']=SOAP_1_2; + if(!empty($this->_actor)) + $options['actor']=$this->_actor; + if(!empty($this->_encoding)) + $options['encoding']=$this->_encoding; + if(!empty($this->_uri)) + $options['uri']=$this->_uri; + if(is_string($this->_classMap)) + { + foreach(preg_split('/\s*,\s*/', $this->_classMap) as $className) + $options['classmap'][$className]=$className; //complex type uses the class name in the wsdl + } + return $options; + } + + /** + * Returns the WSDL content of the SOAP server. + * If {@link getWsdlUri WsdlUri} is set, its content will be returned. + * If not, the {@link setProvider Provider} class will be investigated + * and the WSDL will be automatically genearted. + * @return string the WSDL content of the SOAP server + */ + public function getWsdl() + { + if($this->_wsdlUri==='') + { + $provider=$this->getProvider(); + $providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider; + Prado::using($provider); + if($this->getApplication()->getMode()===TApplicationMode::Performance && ($cache=$this->getApplication()->getCache())!==null) + { + $wsdl=$cache->get(self::WSDL_CACHE_PREFIX.$providerClass); + if(is_string($wsdl)) + return $wsdl; + Prado::using('System.3rdParty.WsdlGen.WsdlGenerator'); + $wsdl=WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding()); + $cache->set(self::WSDL_CACHE_PREFIX.$providerClass,$wsdl); + return $wsdl; + } + else + { + Prado::using('System.3rdParty.WsdlGen.WsdlGenerator'); + return WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding()); + } + } + else + return file_get_contents($this->_wsdlUri); + } + + /** + * @return string the URI for WSDL + */ + public function getWsdlUri() + { + if($this->_wsdlUri==='') + return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID().'.wsdl',false); + else + return $this->_wsdlUri; + } + + /** + * @param string the URI for WSDL + */ + public function setWsdlUri($value) + { + $this->_wsdlUri=$value; + } + + /** + * @return string the URI for the SOAP service + */ + public function getUri() + { + if($this->_uri==='') + return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID(),false); + else + return $this->_uri; + } + + /** + * @param string the URI for the SOAP service + */ + public function setUri($uri) + { + $this->_uri=$uri; + } + + /** + * @return string the SOAP provider class (in namespace format) + */ + public function getProvider() + { + return $this->_provider; + } + + /** + * @param string the SOAP provider class (in namespace format) + */ + public function setProvider($provider) + { + $this->_provider=$provider; + } + + /** + * @return string SOAP version, defaults to empty (meaning not set). + */ + public function getVersion() + { + return $this->_version; + } + + /** + * @param string SOAP version, either '1.1' or '1.2' + * @throws TInvalidDataValueException if neither '1.1' nor '1.2' + */ + public function setVersion($value) + { + if($value==='1.1' || $value==='1.2' || $value==='') + $this->_version=$value; + else + throw new TInvalidDataValueException('soapserver_version_invalid',$value); + } + + /** + * @return string actor of the SOAP service + */ + public function getActor() + { + return $this->_actor; + } + + /** + * @param string actor of the SOAP service + */ + public function setActor($value) + { + $this->_actor=$value; + } + + /** + * @return string encoding of the SOAP service + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * @param string encoding of the SOAP service + */ + public function setEncoding($value) + { + $this->_encoding=$value; + } + + /** + * @return boolean whether the SOAP service is persistent within session. Defaults to false. + */ + public function getSessionPersistent() + { + return $this->_persistent; + } + + /** + * @param boolean whether the SOAP service is persistent within session. + */ + public function setSessionPersistent($value) + { + $this->_persistent=TPropertyValue::ensureBoolean($value); + } + + /** + * @return string comma delimit list of complex type classes. + */ + public function getClassMaps() + { + return $this->_classMap; + } + + /** + * @return string comma delimit list of class names + */ + public function setClassMaps($classes) + { + $this->_classMap = $classes; + } +} + |