Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TSoapService and TSoapServer class file
4
 *
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 &copy; 2005-2008 PradoSoft
9
 * @license http://www.pradosoft.com/license/
10
 * @version $Id: TSoapService.php 2541 2008-10-21 15:05:13Z qiang.xue $
11
 * @package System.Web.Services
12
 */
13
 
14
/**
15
 * TSoapService class
16
 *
17
 * TSoapService processes SOAP requests for a PRADO application.
18
 * TSoapService requires PHP SOAP extension to be loaded.
19
 *
20
 * TSoapService manages a set of SOAP providers. Each SOAP provider
21
 * is a class that implements a set of SOAP methods which are exposed
22
 * to SOAP clients for remote invocation. TSoapService generates WSDL
23
 * automatically for the SOAP providers by default.
24
 *
25
 * To use TSoapService, configure it in the application specification like following:
26
 * <code>
27
 *   <services>
28
 *     <service id="soap" class="System.Web.Services.TSoapService">
29
 *       <soap id="stockquote" provider="MyStockQuote" />
30
 *     </service>
31
 *   </services>
32
 * </code>
33
 *
34
 * The above example specifies a single SOAP provider named "stockquote"
35
 * whose class is "MyStockQuote". A SOAP client can then obtain the WSDL for
36
 * this provider via the following URL:
37
 * <code>
38
 *   http://hostname/path/to/index.php?soap=stockquote.wsdl
39
 * </code>
40
 *
41
 * The WSDL for the provider class "MyStockQuote" is generated based on special
42
 * comment tags in the class. In particular, if a class method's comment
43
 * contains the keyword "@soapmethod", it is considered to be a SOAP method
44
 * and will be exposed to SOAP clients. For example,
45
 * <code>
46
 *   class MyStockQuote {
47
 *      / **
48
 *       * @param string $symbol the stock symbol
49
 *       * @return float the stock price
50
 *       * @soapmethod
51
 *       * /
52
 *      public function getQuote($symbol) {...}
53
 *   }
54
 * </code>
55
 *
56
 * With the above SOAP provider, a typical SOAP client may call the method "getQuote"
57
 * remotely like the following:
58
 * <code>
59
 *   $client=new SoapClient("http://hostname/path/to/index.php?soap=stockquote.wsdl");
60
 *   echo $client->getQuote("ibm");
61
 * </code>
62
 *
63
 * Each <soap> element in the application specification actually configures
64
 * the properties of a SOAP server which defaults to {@link TSoapServer}.
65
 * Therefore, any writable property of {@link TSoapServer} may appear as an attribute
66
 * in the <soap> element. For example, the "provider" attribute refers to
67
 * the {@link TSoapServer::setProvider Provider} property of {@link TSoapServer}.
68
 * The following configuration specifies that the SOAP server is persistent within
69
 * the user session (that means a MyStockQuote object will be stored in session)
70
 * <code>
71
 *   <services>
72
 *     <service id="soap" class="System.Web.Services.TSoapService">
73
 *       <soap id="stockquote" provider="MyStockQuote" SessionPersistent="true" />
74
 *     </service>
75
 *   </services>
76
 * </code>
77
 *
78
 * You may also use your own SOAP server class by specifying the "class" attribute of <soap>.
79
 *
80
 * @author Knut Urdalen <knut.urdalen@gmail.com>
81
 * @author Qiang Xue <qiang.xue@gmail.com>
82
 * @package System.Web.Services
83
 * @since 3.1
84
 */
85
class TSoapService extends TService
86
{
87
	const DEFAULT_SOAP_SERVER='TSoapServer';
88
	const CONFIG_FILE_EXT='.xml';
89
	private $_servers=array();
90
	private $_configFile=null;
91
	private $_wsdlRequest=false;
92
	private $_serverID=null;
93
 
94
	/**
95
	 * Constructor.
96
	 * Sets default service ID to 'soap'.
97
	 */
98
	public function __construct()
99
	{
100
		$this->setID('soap');
101
	}
102
 
103
	/**
104
	 * Initializes this module.
105
	 * This method is required by the IModule interface.
106
	 * @param TXmlElement configuration for this module, can be null
107
	 * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid.
108
	 */
109
	public function init($config)
110
	{
111
		if($this->_configFile!==null)
112
		{
113
 			if(is_file($this->_configFile))
114
 			{
115
				$dom=new TXmlDocument;
116
				$dom->loadFromFile($this->_configFile);
117
				$this->loadConfig($dom);
118
			}
119
			else
120
				throw new TConfigurationException('soapservice_configfile_invalid',$this->_configFile);
121
		}
122
		$this->loadConfig($config);
123
 
124
		$this->resolveRequest();
125
	}
126
 
127
	/**
128
	 * Resolves the request parameter.
129
	 * It identifies the server ID and whether the request is for WSDL.
130
	 * @throws THttpException if the server ID cannot be found
131
	 * @see getServerID
132
	 * @see getIsWsdlRequest
133
	 */
134
	protected function resolveRequest()
135
	{
136
		$serverID=$this->getRequest()->getServiceParameter();
137
		if(($pos=strrpos($serverID,'.wsdl'))===strlen($serverID)-5)
138
		{
139
			$serverID=substr($serverID,0,$pos);
140
			$this->_wsdlRequest=true;
141
		}
142
		else
143
			$this->_wsdlRequest=false;
144
		$this->_serverID=$serverID;
145
		if(!isset($this->_servers[$serverID]))
146
			throw new THttpException(400,'soapservice_request_invalid',$serverID);
147
	}
148
 
149
	/**
150
	 * Loads configuration from an XML element
151
	 * @param TXmlElement configuration node
152
	 * @throws TConfigurationException if soap server id is not specified or duplicated
153
	 */
154
	private function loadConfig($xml)
155
	{
156
		foreach($xml->getElementsByTagName('soap') as $serverXML)
157
		{
158
			$properties=$serverXML->getAttributes();
159
			if(($id=$properties->remove('id'))===null)
160
				throw new TConfigurationException('soapservice_serverid_required');
161
			if(isset($this->_servers[$id]))
162
				throw new TConfigurationException('soapservice_serverid_duplicated',$id);
163
			$this->_servers[$id]=$properties;
164
		}
165
	}
166
 
167
	/**
168
	 * @return string external configuration file. Defaults to null.
169
	 */
170
	public function getConfigFile()
171
	{
172
		return $this->_configFile;
173
	}
174
 
175
	/**
176
	 * @param string external configuration file in namespace format. The file
177
	 * must be suffixed with '.xml'.
178
	 * @throws TInvalidDataValueException if the file is invalid.
179
	 */
180
	public function setConfigFile($value)
181
	{
182
		if(($this->_configFile=Prado::getPathOfNamespace($value,self::CONFIG_FILE_EXT))===null)
183
			throw new TConfigurationException('soapservice_configfile_invalid',$value);
184
	}
185
 
186
	/**
187
	 * Constructs a URL with specified page path and GET parameters.
188
	 * @param string soap server ID
189
	 * @param array list of GET parameters, null if no GET parameters required
190
	 * @param boolean whether to encode the ampersand in URL, defaults to true.
191
	 * @param boolean whether to encode the GET parameters (their names and values), defaults to true.
192
	 * @return string URL for the page and GET parameters
193
	 */
194
	public function constructUrl($serverID,$getParams=null,$encodeAmpersand=true,$encodeGetItems=true)
195
	{
196
		return $this->getRequest()->constructUrl($this->getID(),$serverID,$getParams,$encodeAmpersand,$encodeGetItems);
197
	}
198
 
199
	/**
200
	 * @return boolean whether this is a request for WSDL
201
	 */
202
	public function getIsWsdlRequest()
203
	{
204
		return $this->_wsdlRequest;
205
	}
206
 
207
	/**
208
	 * @return string the SOAP server ID
209
	 */
210
	public function getServerID()
211
	{
212
		return $this->_serverID;
213
	}
214
 
215
	/**
216
	 * Creates the requested SOAP server.
217
	 * The SOAP server is initialized with the property values specified
218
	 * in the configuration.
219
	 * @return TSoapServer the SOAP server instance
220
	 */
221
	protected function createServer()
222
	{
223
		$properties=$this->_servers[$this->_serverID];
224
		if(($serverClass=$properties->remove('class'))===null)
225
			$serverClass=self::DEFAULT_SOAP_SERVER;
226
		Prado::using($serverClass);
227
		$className=($pos=strrpos($serverClass,'.'))!==false?substr($serverClass,$pos+1):$serverClass;
228
		if($className!==self::DEFAULT_SOAP_SERVER && !is_subclass_of($className,self::DEFAULT_SOAP_SERVER))
229
			throw new TConfigurationException('soapservice_server_invalid',$serverClass);
230
		$server=new $className;
231
		$server->setID($this->_serverID);
232
		foreach($properties as $name=>$value)
233
			$server->setSubproperty($name,$value);
234
		return $server;
235
	}
236
 
237
	/**
238
	 * Runs the service.
239
	 * If the service parameter ends with '.wsdl', it will serve a WSDL file for
240
	 * the specified soap server.
241
	 * Otherwise, it will handle the soap request using the specified server.
242
	 */
243
	public function run()
244
	{
245
		Prado::trace("Running SOAP service",'System.Web.Services.TSoapService');
246
		$server=$this->createServer();
247
		$this->getResponse()->setContentType('text/xml');
248
		$this->getResponse()->setCharset($server->getEncoding());
249
		if($this->getIsWsdlRequest())
250
		{
251
			// server WSDL file
252
			Prado::trace("Generating WSDL",'System.Web.Services.TSoapService');
253
			$this->getResponse()->write($server->getWsdl());
254
		}
255
		else
256
		{
257
			// provide SOAP service
258
			Prado::trace("Handling SOAP request",'System.Web.Services.TSoapService');
259
			$server->run();
260
		}
261
	}
262
}
263
 
264
 
265
/**
266
 * TSoapServer class.
267
 *
268
 * TSoapServer is a wrapper of the PHP SoapServer class.
269
 * It associates a SOAP provider class to the SoapServer object.
270
 * It also manages the URI for the SOAP service and WSDL.
271
 *
272
 * @author Qiang Xue <qiang.xue@gmail.com>
273
 * @version $Id: TSoapService.php 2541 2008-10-21 15:05:13Z qiang.xue $
274
 * @package System.Web.Services
275
 * @since 3.1
276
 */
277
class TSoapServer extends TApplicationComponent
278
{
279
	const WSDL_CACHE_PREFIX='wsdl.';
280
 
281
	private $_id;
282
	private $_provider;
283
 
284
	private $_version='';
285
	private $_actor='';
286
	private $_encoding='';
287
	private $_uri='';
288
	private $_classMap;
289
	private $_persistent=false;
290
	private $_wsdlUri='';
291
 
292
	private $_requestedMethod;
293
 
294
	private $_server;
295
 
296
	/**
297
	 * @return string the ID of the SOAP server
298
	 */
299
	public function getID()
300
	{
301
		return $this->_id;
302
	}
303
 
304
	/**
305
	 * @param string the ID of the SOAP server
306
	 * @throws TInvalidDataValueException if the ID ends with '.wsdl'.
307
	 */
308
	public function setID($id)
309
	{
310
		if(strrpos($this->_id,'.wsdl')===strlen($this->_id)-5)
311
			throw new TInvalidDataValueException('soapserver_id_invalid',$id);
312
		$this->_id=$id;
313
	}
314
 
315
	/**
316
	 * Handles the SOAP request.
317
	 */
318
	public function run()
319
	{
320
		if(($provider=$this->getProvider())!==null)
321
		{
322
			Prado::using($provider);
323
			$providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
324
			$this->guessMethodCallRequested($providerClass);
325
			$server=$this->createServer();
326
			$server->setClass($providerClass, $this);
327
			if($this->_persistent)
328
				$server->setPersistence(SOAP_PERSISTENCE_SESSION);
329
		}
330
		else
331
			$server=$this->createServer();
332
		try
333
		{
334
			$server->handle();
335
		}
336
		catch (Exception $e)
337
		{
338
			if($this->getApplication()->getMode()===TApplicationMode::Debug)
339
				$this->fault($e->getMessage(), $e->__toString());
340
			else
341
				$this->fault($e->getMessage());
342
		}
343
	}
344
 
345
	/**
346
	 * Generate a SOAP fault message.
347
	 * @param string message title
348
	 * @param mixed message details
349
	 * @param string message code, defalt is 'SERVER'.
350
	 * @param string actors
351
	 * @param string message name
352
	 */
353
	public function fault($title, $details='', $code='SERVER', $actor='', $name='')
354
	{
355
		Prado::trace('SOAP-Fault '.$code. ' '.$title.' : '.$details, 'System.Web.Services.TSoapService');
356
		$this->_server->fault($code, $title, $actor, $details, $name);
357
	}
358
 
359
	/**
360
	 * Guess the SOAP method request from the actual SOAP message
361
	 *
362
	 * @param string $class current handler class.
363
	 */
364
	protected function guessMethodCallRequested($class)
365
	{
366
		$namespace = $class.'wsdl';
367
		$message = file_get_contents("php://input");
368
		$matches= array();
369
		if(preg_match('/xmlns:([^=]+)="urn:'.$namespace.'"/', $message, $matches))
370
		{
371
			if(preg_match('/<'.$matches[1].':([a-zA-Z_]+[a-zA-Z0-9_]+)/', $message, $method))
372
			{
373
				$this->_requestedMethod = $method[1];
374
			}
375
		}
376
	}
377
 
378
	/**
379
	 * Soap method guessed from the SOAP message received.
380
	 * @return string soap method request, null if not found.
381
	 */
382
	public function getRequestedMethod()
383
	{
384
		return $this->_requestedMethod;
385
	}
386
 
387
	/**
388
	 * Creates the SoapServer instance.
389
	 * @return SoapServer
390
	 */
391
	protected function createServer()
392
	{
393
		if($this->_server===null)
394
		{
395
			if($this->getApplication()->getMode()===TApplicationMode::Debug)
396
				ini_set("soap.wsdl_cache_enabled",0);
397
			$this->_server = new SoapServer($this->getWsdlUri(),$this->getOptions());
398
		}
399
		return $this->_server;
400
	}
401
 
402
	/**
403
	 * @return array options for creating SoapServer instance
404
	 */
405
	protected function getOptions()
406
	{
407
		$options=array();
408
		if($this->_version==='1.1')
409
			$options['soap_version']=SOAP_1_1;
410
		else if($this->_version==='1.2')
411
			$options['soap_version']=SOAP_1_2;
412
		if(!empty($this->_actor))
413
			$options['actor']=$this->_actor;
414
		if(!empty($this->_encoding))
415
			$options['encoding']=$this->_encoding;
416
		if(!empty($this->_uri))
417
			$options['uri']=$this->_uri;
418
		if(is_string($this->_classMap))
419
		{
420
			foreach(preg_split('/\s*,\s*/', $this->_classMap) as $className)
421
				$options['classmap'][$className]=$className; //complex type uses the class name in the wsdl
422
		}
423
		return $options;
424
	}
425
 
426
	/**
427
	 * Returns the WSDL content of the SOAP server.
428
	 * If {@link getWsdlUri WsdlUri} is set, its content will be returned.
429
	 * If not, the {@link setProvider Provider} class will be investigated
430
	 * and the WSDL will be automatically genearted.
431
	 * @return string the WSDL content of the SOAP server
432
	 */
433
	public function getWsdl()
434
	{
435
		if($this->_wsdlUri==='')
436
		{
437
			$provider=$this->getProvider();
438
			$providerClass=($pos=strrpos($provider,'.'))!==false?substr($provider,$pos+1):$provider;
439
			Prado::using($provider);
440
			if($this->getApplication()->getMode()===TApplicationMode::Performance && ($cache=$this->getApplication()->getCache())!==null)
441
			{
442
				$wsdl=$cache->get(self::WSDL_CACHE_PREFIX.$providerClass);
443
				if(is_string($wsdl))
444
					return $wsdl;
445
				Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
446
				$wsdl=WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
447
				$cache->set(self::WSDL_CACHE_PREFIX.$providerClass,$wsdl);
448
				return $wsdl;
449
			}
450
			else
451
			{
452
				Prado::using('System.3rdParty.WsdlGen.WsdlGenerator');
453
				return WsdlGenerator::generate($providerClass, $this->getUri(), $this->getEncoding());
454
			}
455
		}
456
		else
457
			return file_get_contents($this->_wsdlUri);
458
	}
459
 
460
	/**
461
	 * @return string the URI for WSDL
462
	 */
463
	public function getWsdlUri()
464
	{
465
		if($this->_wsdlUri==='')
466
			return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID().'.wsdl',false);
467
		else
468
			return $this->_wsdlUri;
469
	}
470
 
471
	/**
472
	 * @param string the URI for WSDL
473
	 */
474
	public function setWsdlUri($value)
475
	{
476
		$this->_wsdlUri=$value;
477
	}
478
 
479
	/**
480
	 * @return string the URI for the SOAP service
481
	 */
482
	public function getUri()
483
	{
484
		if($this->_uri==='')
485
			return $this->getRequest()->getBaseUrl().$this->getService()->constructUrl($this->getID(),false);
486
		else
487
			return $this->_uri;
488
	}
489
 
490
	/**
491
	 * @param string the URI for the SOAP service
492
	 */
493
	public function setUri($uri)
494
	{
495
		$this->_uri=$uri;
496
	}
497
 
498
	/**
499
	 * @return string the SOAP provider class (in namespace format)
500
	 */
501
	public function getProvider()
502
	{
503
		return $this->_provider;
504
	}
505
 
506
	/**
507
	 * @param string the SOAP provider class (in namespace format)
508
	 */
509
	public function setProvider($provider)
510
	{
511
		$this->_provider=$provider;
512
	}
513
 
514
	/**
515
	 * @return string SOAP version, defaults to empty (meaning not set).
516
	 */
517
	public function getVersion()
518
	{
519
		return $this->_version;
520
	}
521
 
522
	/**
523
	 * @param string SOAP version, either '1.1' or '1.2'
524
	 * @throws TInvalidDataValueException if neither '1.1' nor '1.2'
525
	 */
526
	public function setVersion($value)
527
	{
528
		if($value==='1.1' || $value==='1.2' || $value==='')
529
			$this->_version=$value;
530
		else
531
			throw new TInvalidDataValueException('soapserver_version_invalid',$value);
532
	}
533
 
534
	/**
535
	 * @return string actor of the SOAP service
536
	 */
537
	public function getActor()
538
	{
539
		return $this->_actor;
540
	}
541
 
542
	/**
543
	 * @param string actor of the SOAP service
544
	 */
545
	public function setActor($value)
546
	{
547
		$this->_actor=$value;
548
	}
549
 
550
	/**
551
	 * @return string encoding of the SOAP service
552
	 */
553
	public function getEncoding()
554
	{
555
		return $this->_encoding;
556
	}
557
 
558
	/**
559
	 * @param string encoding of the SOAP service
560
	 */
561
	public function setEncoding($value)
562
	{
563
		$this->_encoding=$value;
564
	}
565
 
566
	/**
567
	 * @return boolean whether the SOAP service is persistent within session. Defaults to false.
568
	 */
569
	public function getSessionPersistent()
570
	{
571
		return $this->_persistent;
572
	}
573
 
574
	/**
575
	 * @param boolean whether the SOAP service is persistent within session.
576
	 */
577
	public function setSessionPersistent($value)
578
	{
579
		$this->_persistent=TPropertyValue::ensureBoolean($value);
580
	}
581
 
582
	/**
583
	 * @return string comma delimit list of complex type classes.
584
	 */
585
	public function getClassMaps()
586
	{
587
		return $this->_classMap;
588
	}
589
 
590
	/**
591
	 * @return string comma delimit list of class names
592
	 */
593
	public function setClassMaps($classes)
594
	{
595
		$this->_classMap = $classes;
596
	}
597
}
598