Passed
Push — master ( 596a36...36fff2 )
by Fabio
05:01
created

TSoapService::constructUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * TSoapService and TSoapServer class file
4
 *
5
 * @author Knut Urdalen <[email protected]>
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Web\Services
10
 */
11
12
namespace Prado\Web\Services;
13
14
use Prado\Exceptions\TConfigurationException;
15
use Prado\Exceptions\THttpException;
16
use Prado\Prado;
17
use Prado\TApplication;
18
use Prado\Xml\TXmlDocument;
19
20
/**
21
 * TSoapService class
22
 *
23
 * TSoapService processes SOAP requests for a PRADO application.
24
 * TSoapService requires PHP SOAP extension to be loaded.
25
 *
26
 * TSoapService manages a set of SOAP providers. Each SOAP provider
27
 * is a class that implements a set of SOAP methods which are exposed
28
 * to SOAP clients for remote invocation. TSoapService generates WSDL
29
 * automatically for the SOAP providers by default.
30
 *
31
 * To use TSoapService, configure it in the application specification like following:
32
 * <code>
33
 *   <services>
34
 *     <service id="soap" class="Prado\Web\Services\TSoapService">
35
 *       <soap id="stockquote" provider="MyStockQuote" />
36
 *     </service>
37
 *   </services>
38
 * </code>
39
 * PHP configuration style:
40
 * <code>
41
 *  'services' => array(
42
 *    'soap' => array(
43
 *     'class' => 'Prado\Web\Services\TSoapService'
44
 *     'properties' => array(
45
 *       'provider' => 'MyStockQuote'
46
 *	   )
47
 *    )
48
 *  )
49
 * </code>
50
 *
51
 * The WSDL for the provider class "MyStockQuote" is generated based on special
52
 * comment tags in the class. In particular, if a class method's comment
53
 * contains the keyword "@soapmethod", it is considered to be a SOAP method
54
 * and will be exposed to SOAP clients. For example,
55
 * <code>
56
 *   class MyStockQuote {
57
 *      / **
58
 *       * @param string $symbol the stock symbol
59
 *       * @return float the stock price
60
 *       * @soapmethod
61
 *       * /
62
 *      public function getQuote($symbol) {...}
63
 *   }
64
 * </code>
65
 *
66
 * With the above SOAP provider, a typical SOAP client may call the method "getQuote"
67
 * remotely like the following:
68
 * <code>
69
 *   $client=new SoapClient("http://hostname/path/to/index.php?soap=stockquote.wsdl");
70
 *   echo $client->getQuote("ibm");
71
 * </code>
72
 *
73
 * Each <soap> element in the application specification actually configures
74
 * the properties of a SOAP server which defaults to {@link TSoapServer}.
75
 * Therefore, any writable property of {@link TSoapServer} may appear as an attribute
76
 * in the <soap> element. For example, the "provider" attribute refers to
77
 * the {@link TSoapServer::setProvider Provider} property of {@link TSoapServer}.
78
 * The following configuration specifies that the SOAP server is persistent within
79
 * the user session (that means a MyStockQuote object will be stored in session)
80
 * <code>
81
 *   <services>
82
 *     <service id="soap" class="Prado\Web\Services\TSoapService">
83
 *       <soap id="stockquote" provider="MyStockQuote" SessionPersistent="true" />
84
 *     </service>
85
 *   </services>
86
 * </code>
87
 *
88
 * You may also use your own SOAP server class by specifying the "class" attribute of <soap>.
89
 *
90
 * @author Knut Urdalen <[email protected]>
91
 * @author Qiang Xue <[email protected]>
92
 * @author Carl G. Mathisen <[email protected]>
93
 * @package Prado\Web\Services
94
 * @since 3.1
95
 */
96
class TSoapService extends \Prado\TService
97
{
98
	const DEFAULT_SOAP_SERVER = 'Prado\Web\Services\TSoapServer';
99
	private $_servers = [];
100
	private $_configFile;
101
	private $_wsdlRequest = false;
102
	private $_serverID;
103
104
	/**
105
	 * Constructor.
106
	 * Sets default service ID to 'soap'.
107
	 */
108
	public function __construct()
109
	{
110
		$this->setID('soap');
111
	}
112
113
	/**
114
	 * Initializes this module.
115
	 * This method is required by the IModule interface.
116
	 * @param \Prado\Xml\TXmlElement $config configuration for this module, can be null
117
	 * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.
118
	 */
119
	public function init($config)
120
	{
121
		if ($this->_configFile !== null) {
122
			if (is_file($this->_configFile)) {
123
				$dom = new TXmlDocument;
124
				$dom->loadFromFile($this->_configFile);
125
				$this->loadConfig($dom);
126
			} else {
127
				throw new TConfigurationException('soapservice_configfile_invalid', $this->_configFile);
128
			}
129
		}
130
		$this->loadConfig($config);
131
132
		$this->resolveRequest();
133
	}
134
135
	/**
136
	 * Resolves the request parameter.
137
	 * It identifies the server ID and whether the request is for WSDL.
138
	 * @throws THttpException if the server ID cannot be found
139
	 * @see getServerID
140
	 * @see getIsWsdlRequest
141
	 */
142
	protected function resolveRequest()
143
	{
144
		$serverID = $this->getRequest()->getServiceParameter();
145
		if (($pos = strrpos($serverID, '.wsdl')) === strlen($serverID) - 5) {
146
			$serverID = substr($serverID, 0, $pos);
147
			$this->_wsdlRequest = true;
148
		} else {
149
			$this->_wsdlRequest = false;
150
		}
151
		$this->_serverID = $serverID;
152
		if (!isset($this->_servers[$serverID])) {
153
			throw new THttpException(400, 'soapservice_request_invalid', $serverID);
154
		}
155
	}
156
157
	/**
158
	 * Loads configuration from an XML element
159
	 * @param mixed $config configuration node
160
	 * @throws TConfigurationException if soap server id is not specified or duplicated
161
	 */
162
	private function loadConfig($config)
163
	{
164
		if ($this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
165
			if (is_array($config)) {
166
				foreach ($config['soap'] as $id => $server) {
167
					$properties = $server['properties'] ?? [];
168
					if (isset($this->_servers[$id])) {
169
						throw new TConfigurationException('soapservice_serverid_duplicated', $id);
170
					}
171
					$this->_servers[$id] = $properties;
172
				}
173
			}
174
		} else {
175
			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
				}
180
				if (isset($this->_servers[$id])) {
181
					throw new TConfigurationException('soapservice_serverid_duplicated', $id);
182
				}
183
				$this->_servers[$id] = $properties;
184
			}
185
		}
186
	}
187
188
	/**
189
	 * @return string external configuration file. Defaults to null.
190
	 */
191
	public function getConfigFile()
192
	{
193
		return $this->_configFile;
194
	}
195
196
	/**
197
	 * @param string $value external configuration file in namespace format. The file
198
	 * must be suffixed with '.xml'.
199
	 * @throws TInvalidDataValueException if the file is invalid.
200
	 */
201
	public function setConfigFile($value)
202
	{
203
		if (($this->_configFile = Prado::getPathOfNamespace($value, Prado::getApplication()->getConfigurationFileExt())) === null) {
0 ignored issues
show
introduced by
The condition $this->_configFile = Pra...tionFileExt()) === null is always false.
Loading history...
204
			throw new TConfigurationException('soapservice_configfile_invalid', $value);
205
		}
206
	}
207
208
	/**
209
	 * Constructs a URL with specified page path and GET parameters.
210
	 * @param string $serverID soap server ID
211
	 * @param array $getParams list of GET parameters, null if no GET parameters required
212
	 * @param bool $encodeAmpersand whether to encode the ampersand in URL, defaults to true.
213
	 * @param bool $encodeGetItems whether to encode the GET parameters (their names and values), defaults to true.
214
	 * @return string URL for the page and GET parameters
215
	 */
216
	public function constructUrl($serverID, $getParams = null, $encodeAmpersand = true, $encodeGetItems = true)
217
	{
218
		return $this->getRequest()->constructUrl($this->getID(), $serverID, $getParams, $encodeAmpersand, $encodeGetItems);
219
	}
220
221
	/**
222
	 * @return bool whether this is a request for WSDL
223
	 */
224
	public function getIsWsdlRequest()
225
	{
226
		return $this->_wsdlRequest;
227
	}
228
229
	/**
230
	 * @return string the SOAP server ID
231
	 */
232
	public function getServerID()
233
	{
234
		return $this->_serverID;
235
	}
236
237
	/**
238
	 * Creates the requested SOAP server.
239
	 * The SOAP server is initialized with the property values specified
240
	 * in the configuration.
241
	 * @return TSoapServer the SOAP server instance
242
	 */
243
	protected function createServer()
244
	{
245
		$properties = $this->_servers[$this->_serverID];
246
		$serverClass = $properties->remove('class');
247
		if ($serverClass === null) {
248
			$serverClass = self::DEFAULT_SOAP_SERVER;
249
		}
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
		}
255
		$server = new $className;
256
		$server->setID($this->_serverID);
257
		foreach ($properties as $name => $value) {
258
			$server->setSubproperty($name, $value);
259
		}
260
		return $server;
261
	}
262
263
	/**
264
	 * Runs the service.
265
	 * If the service parameter ends with '.wsdl', it will serve a WSDL file for
266
	 * the specified soap server.
267
	 * Otherwise, it will handle the soap request using the specified server.
268
	 */
269
	public function run()
270
	{
271
		Prado::trace("Running SOAP service", 'Prado\Web\Services\TSoapService');
272
		$server = $this->createServer();
273
		$this->getResponse()->setContentType('text/xml');
274
		$this->getResponse()->setCharset($server->getEncoding());
275
		if ($this->getIsWsdlRequest()) {
276
			// server WSDL file
277
			Prado::trace("Generating WSDL", 'Prado\Web\Services\TSoapService');
278
			$this->getResponse()->clear();
279
			$this->getResponse()->write($server->getWsdl());
280
		} else {
281
			// provide SOAP service
282
			Prado::trace("Handling SOAP request", 'Prado\Web\Services\TSoapService');
283
			$server->run();
284
		}
285
	}
286
}
287