Ripcord_Client   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 255
rs 8.8798
c 0
b 0
f 0
ccs 0
cts 155
cp 0
wmc 44
lcom 0
cbo 6

3 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 8
F __call() 0 110 33
A __get() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like Ripcord_Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Ripcord_Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Ripcord is an easy to use XML-RPC library for PHP.
4
 * @package Ripcord
5
 * @author Auke van Slooten <[email protected]>
6
 * @copyright Copyright (C) 2010, Muze <www.muze.nl>
7
 * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
8
 * @version Ripcord 0.9 - PHP 5
9
 */
10
11
/**
12
 * Includes the static ripcord factory class and exceptions
13
 */
14
require_once(dirname(__FILE__).'/ripcord.php');
15
16
/**
17
 * This class implements a simple RPC client, for XML-RPC, (simplified) SOAP 1.1 or Simple RPC. The client abstracts
18
 * the entire RPC process behind native PHP methods. Any method defined by the rpc server can be called as if it was
19
 * a native method of the rpc client.
20
 *
21
 *  E.g.
22
 *  <code>
23
 *  <?php
24
 *    $client = ripcord::client( 'http://www.moviemeter.nl/ws' );
25
 *    $score = $client->film->getScore( 'e3dee9d19a8c3af7c92f9067d2945b59', 500 );
26
 *  ?>
27
 *  </code>
28
 *
29
 * The client has a simple interface for the system.multiCall method:
30
 * <code>
31
 * <?php
32
 *  $client = ripcord::client( 'http://ripcord.muze.nl/ripcord.php' );
33
 *  $client->system->multiCall()->start();
34
 *  ripcord::bind( $methods, $client->system->listMethods() );
35
 *  ripcord::bind( $foo, $client->getFoo() );
36
 *  $client->system->multiCall()->execute();
37
 * ?>
38
 * </code>
39
 *
40
 * The soap client can only handle the basic php types and doesn't understand xml namespaces. Use PHP's SoapClient
41
 * for complex soap calls. This client cannot parse wsdl.
42
 * If you want to skip the ripcord::client factory method, you _must_ provide a transport object explicitly.
43
 *
44
 * @link  http://wiki.moviemeter.nl/index.php/API Moviemeter API documentation
45
 * @package Ripcord
46
 */
47
class Ripcord_Client
48
{
49
	/**
50
	 * The url of the rpc server
51
	 */
52
	private $_url = '';
53
54
	/**
55
	 * The transport object, used to post requests.
56
	 */
57
	private $_transport = null;
58
59
	/**
60
	 * A list of output options, used with the xmlrpc_encode_request method.
61
	 * @see Ripcord_Server::setOutputOption()
62
	 */
63
	private $_outputOptions = array(
64
		"output_type" => "xml",
65
		"verbosity" => "pretty",
66
		"escaping" => array("markup"),
67
		"version" => "xmlrpc",
68
		"encoding" => "utf-8"
69
	);
70
71
	/**
72
	 * The namespace to use when calling a method.
73
	 */
74
	private $_namespace = null;
75
76
	/**
77
	 * A reference to the root client object. This is so when you use namespaced sub clients, you can always
78
	 * find the _response and _request data in the root client.
79
	 */
80
	private $_rootClient = null;
81
82
	/**
83
	 * A flag to indicate whether or not to preemptively clone objects passed as arguments to methods, see
84
	 * php bug #50282. Only correctly set in the rootClient.
85
	 */
86
	private $_cloneObjects = false;
87
88
	/**
89
	 * A flag to indicate if we are in a multiCall block. Start this with $client->system->multiCall()->start()
90
	 */
91
	protected $_multiCall = false;
92
93
	/**
94
	 * A list of deferred encoded calls.
95
	 */
96
	protected $_multiCallArgs = array();
97
98
	/**
99
	  * The exact response from the rpc server. For debugging purposes.
100
	 */
101
	public $_response = '';
102
103
	/**
104
	 * The exact request from the client. For debugging purposes.
105
	 */
106
	public $_request = '';
107
108
	/**
109
	 * Whether or not to throw exceptions when an xml-rpc fault is returned by the server. Default is false.
110
	 */
111
	public $_throwExceptions = false;
112
113
	/**
114
	 * Whether or not to decode the XML-RPC datetime and base64 types to unix timestamp and binary string
115
	 * respectively.
116
	 */
117
	public $_autoDecode = true;
118
119
	/**
120
	 * The constructor for the RPC client.
121
	 * @param string $url The url of the rpc server
122
	 * @param array $options Optional. A list of outputOptions. See {@link Ripcord_Server::setOutputOption()}
123
	 * @param object $rootClient Optional. Used internally when using namespaces.
124
	 * @throws Ripcord_ConfigurationException (ripcord::xmlrpcNotInstalled) when the xmlrpc extension is not available.
125
	 */
126
	public function __construct( $url, array $options = null, $transport = null, $rootClient = null )
127
	{
128
		if ( !isset($rootClient) ) {
129
			$rootClient = $this;
130
			if ( !function_exists( 'xmlrpc_encode_request' ) )
131
			{
132
				throw new Ripcord_ConfigurationException('PHP XMLRPC library is not installed',
133
					ripcord::xmlrpcNotInstalled);
134
			}
135
			$version = explode('.', phpversion() );
136
			if ( (0 + $version[0]) == 5) {
137
				if ( ( 0 + $version[1]) < 2 ) {
138
					$this->_cloneObjects = true; // workaround for bug #50282
139
				}
140
			}
141
		}
142
		$this->_rootClient = $rootClient;
143
		$this->_url = $url;
144
		if ( isset($options) )
145
		{
146
			if ( isset($options['namespace']) )
147
			{
148
				$this->_namespace = $options['namespace'];
149
				unset( $options['namespace'] );
150
			}
151
			$this->_outputOptions = $options;
152
		}
153
		if ( isset($transport) ) {
154
			$this->_transport = $transport;
155
		}
156
	}
157
158
	/**
159
	 * This method catches any native method called on the client and calls it on the rpc server instead. It automatically
160
	 * parses the resulting xml and returns native php type results.
161
	 * @throws Ripcord_InvalidArgumentException (ripcord::notRipcordCall) when handling a multiCall and the
162
	 * arguments passed do not have the correct method call information
163
	 * @throws Ripcord_RemoteException when _throwExceptions is true and the server returns an XML-RPC Fault.
164
	 */
165
	public function __call($name, $args)
166
	{
167
		if ( isset($this->_namespace) )
168
		{
169
			$name = $this->_namespace . '.' . $name;
170
		}
171
172
		if ( $name === 'system.multiCall' || $name == 'system.multicall' )
173
		{
174
			if ( !$args || ( is_array($args) && count($args)==0 ) )
175
			{
176
				// multiCall is called without arguments, so return the fetch interface object
177
				return new Ripcord_Client_MultiCall( $this->_rootClient, $name );
178
			} else if ( is_array( $args ) && (count( $args ) == 1) &&
179
				is_array( $args[0] )  && !isset( $args[0]['methodName'] ) )
180
			{
181
				// multicall is called with a simple array of calls.
182
				$args = $args[0];
183
			}
184
			$this->_rootClient->_multiCall = false;
185
			$params = array();
186
			$bound = array();
187
			foreach ( $args as $key => $arg )
188
			{
189
				if ( !is_a( $arg, 'Ripcord_Client_Call' ) &&
190
					(!is_array($arg) || !isset($arg['methodName']) ) )
191
				{
192
					throw new Ripcord_InvalidArgumentException(
193
						'Argument '.$key.' is not a valid Ripcord call',
194
							ripcord::notRipcordCall);
195
				}
196
				if ( is_a( $arg, 'Ripcord_Client_Call' ) )
197
				{
198
					$arg->index  = count( $params );
199
					$params[]    = $arg->encode();
200
				}
201
				else
202
				{
203
					$arg['index'] = count( $params );
204
					$params[]    = array(
205
						'methodName' => $arg['methodName'],
206
						'params'     => isset($arg['params']) ?
207
							(array) $arg['params'] : array()
208
					);
209
				}
210
				$bound[$key] = $arg;
211
			}
212
			$args = array( $params );
213
			$this->_rootClient->_multiCallArgs = array();
214
		}
215
		if ( $this->_rootClient->_multiCall ) {
216
			$call = new Ripcord_Client_Call( $name, $args );
217
			$this->_rootClient->_multiCallArgs[] = $call;
218
			return $call;
219
		}
220
		if ($this->_rootClient->_cloneObjects) { //workaround for php bug 50282
221
			foreach( $args as $key => $arg) {
222
				if (is_object($arg)) {
223
					$args[$key] = clone $arg;
224
				}
225
			}
226
		}
227
		$request  = xmlrpc_encode_request( $name, $args, $this->_outputOptions );
228
		$response = $this->_transport->post( $this->_url, $request );
229
		$result   = xmlrpc_decode( $response );
230
		$this->_rootClient->_request  = $request;
231
		$this->_rootClient->_response = $response;
232
		if ( ripcord::isFault( $result ) && $this->_throwExceptions )
233
		{
234
			throw new Ripcord_RemoteException($result['faultString'], $result['faultCode']);
235
		}
236
		if ( isset($bound) && is_array( $bound ) )
237
		{
238
			foreach ( $bound as $key => $callObject )
239
			{
240
				if ( is_a( $callObject, 'Ripcord_Client_Call' ) )
241
				{
242
					$returnValue = $result[$callObject->index];
243
				}
244
				else
245
				{
246
					$returnValue = $result[$callObject['index']];
247
				}
248
				if ( is_array( $returnValue ) && count( $returnValue ) == 1 )
249
				{
250
					// XML-RPC specification says that non-fault results must be in a single item array
251
					$returnValue = current($returnValue);
252
				}
253
				if ($this->_autoDecode)
254
				{
255
					$type = xmlrpc_get_type($returnValue);
256
					switch ($type)
257
					{
258
						case 'base64' :
259
							$returnValue = ripcord::binary($returnValue);
260
						break;
261
						case 'datetime' :
262
							$returnValue = ripcord::timestamp($returnValue);
263
						break;
264
					}
265
				}
266
				if ( is_a( $callObject, 'Ripcord_Client_Call' ) ) {
267
					$callObject->bound = $returnValue;
268
				}
269
				$bound[$key] = $returnValue;
270
			}
271
			$result = $bound;
272
		}
273
		return $result;
274
	}
275
276
	/**
277
	 * This method catches any reference to properties of the client and uses them as a namespace. The
278
	 * property is automatically created as a new instance of the rpc client, with the name of the property
279
	 * as a namespace.
280
	 * @param string $name The name of the namespace
281
	 * @return object A Ripcord Client with the given namespace set.
282
	 */
283
	public function __get($name)
284
	{
285
		$result = null;
286
		if ( !isset($this->{$name}) )
287
		{
288
			$result = new Ripcord_Client(
289
				$this->_url,
290
				array_merge($this->_outputOptions, array(
291
					'namespace' => $this->_namespace ?
292
						$this->_namespace . '.' . $name : $name
293
				) ),
294
				$this->_transport,
295
				$this->_rootClient
296
			);
297
			$this->{$name} = $result;
298
		}
299
		return $result;
300
	}
301
}
302
303
/**
304
 * This class provides the fetch interface for system.multiCall. It is returned
305
 * when calling $client->system->multiCall() with no arguments. Upon construction
306
 * it puts the originating client into multiCall deferred mode. The client will
307
 * gather the requested method calls instead of executing them immediately. It
308
 * will them execute all of them, in order, when calling
309
 * $client->system->multiCall()->fetch().
310
 * This class extends Ripcord_Client only so it has access to its protected _multiCall
311
 * property.
312
 */
313
class Ripcord_Client_MultiCall extends Ripcord_Client
314
{
315
316
	/*
317
	 * The reference to the originating client to put into multiCall mode.
318
	 */
319
	private $client = null;
320
321
	/*
322
	 * This method creates a new multiCall fetch api object.
323
	 */
324
	public function __construct( $client, $methodName = 'system.multiCall' )
325
	{
326
		$this->client = $client;
327
		$this->methodName = $methodName;
0 ignored issues
show
Bug introduced by
The property methodName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
328
	}
329
330
	/*
331
	 * This method puts the client into multiCall mode. While in this mode all
332
	 * method calls are collected as deferred calls (Ripcord_Client_Call).
333
	 */
334
	public function start()
335
	{
336
		$this->client->_multiCall = true;
337
	}
338
339
	/*
340
	 * This method finally calls the clients multiCall method with all deferred
341
	 * method calls since multiCall mode was enabled.
342
	 */
343
	public function execute()
344
	{
345
		if ($this->methodName=='system.multiCall') {
346
			return $this->client->system->multiCall( $this->client->_multiCallArgs );
347
		} else { // system.multicall
348
			return $this->client->system->multicall( $this->client->_multiCallArgs );
349
		}
350
	}
351
352
}
353
354
/**
355
 *  This class is used with the Ripcord_Client when calling system.multiCall. Instead of immediately calling the method on the rpc server,
356
 *  a Ripcord_Client_Call  object is created with all the information needed to call the method using the multicall parameters. The call object is
357
 *  returned immediately and is used as input parameter for the multiCall call. The result of the call can be bound to a php variable. This
358
 *  variable will be filled with the result of the call when it is available.
359
 * @package Ripcord
360
 */
361
class Ripcord_Client_Call
362
{
363
	/**
364
	 * The method to call on the rpc server
365
	 */
366
	public $method = null;
367
368
	/**
369
	 * The arguments to pass on to the method.
370
	 */
371
	public $params = array();
372
373
	/**
374
	 * The index in the multicall request array, if any.
375
	 */
376
	public $index  = null;
377
378
	/**
379
	 * A reference to the php variable to fill with the result of the call, if any.
380
	 */
381
	public $bound  = null;
382
383
	/**
384
	 * The constructor for the Ripcord_Client_Call class.
385
	 * @param string $method The name of the rpc method to call
386
	 * @param array $params The parameters for the rpc method.
387
	 */
388
	public function __construct($method, $params)
389
	{
390
		$this->method = $method;
391
		$this->params = $params;
392
	}
393
394
	/**
395
	 * This method allows you to bind a php variable to the result of this method call.
396
	 * When the method call's result is available, the php variable will be filled with
397
	 * this result.
398
	 * @param mixed $bound The variable to bind the result from this call to.
399
	 * @return object Returns this object for chaining.
400
	 */
401
	public function bind(&$bound)
402
	{
403
		$this->bound =& $bound;
404
		return $this;
405
	}
406
407
	/**
408
	 * This method returns the correct format for a multiCall argument.
409
	 * @return array An array with the methodName and params
410
	 */
411
	public function encode() {
412
		return array(
413
			'methodName' => $this->method,
414
			'params' => (array) $this->params
415
		);
416
	}
417
418
}
419
420
/**
421
 * This interface describes the minimum interface needed for the transport object used by the
422
 * Ripcord_Client
423
 * @package Ripcord
424
 */
425
interface Ripcord_Transport
426
{
427
	/**
428
	 * This method must post the request to the given url and return the results.
429
	 * @param string $url The url to post to.
430
	 * @param string $request The request to post.
431
	 * @return string The server response
432
	 */
433
	public function post( $url, $request );
434
}
435
436
/**
437
 * This class implements the Ripcord_Transport interface using PHP streams.
438
 * @package Ripcord
439
 */
440
class  Ripcord_Transport_Stream implements Ripcord_Transport
441
{
442
	/**
443
	 * A list of stream context options.
444
	 */
445
	private $options = array();
446
447
	/**
448
	 * Contains the headers sent by the server.
449
	 */
450
	public $responseHeaders = null;
451
452
	/**
453
	 * This is the constructor for the Ripcord_Transport_Stream class.
454
	 * @param array $contextOptions Optional. An array with stream context options.
455
	 */
456
	public function __construct( $contextOptions = null )
457
	{
458
		if ( isset($contextOptions) )
459
		{
460
			$this->options = $contextOptions;
461
		}
462
	}
463
464
	/**
465
	 * This method posts the request to the given url.
466
	 * @param string $url The url to post to.
467
	 * @param string $request The request to post.
468
	 * @return string The server response
469
	 * @throws Ripcord_TransportException (ripcord::cannotAccessURL) when the given URL cannot be accessed for any reason.
470
	 */
471
	public function post( $url, $request )
472
	{
473
		$options = array_merge(
474
			$this->options,
475
			array(
476
				'http' => array(
477
					'method' => "POST",
478
					'header' => "Content-Type: text/xml",
479
					'content' => $request
480
				)
481
			)
482
		);
483
		$context = stream_context_create( $options );
484
		$result  = @file_get_contents( $url, false, $context );
485
		$this->responseHeaders = $http_response_header;
486
		if ( !$result )
487
		{
488
			throw new Ripcord_TransportException( 'Could not access ' . $url,
489
				ripcord::cannotAccessURL );
490
		}
491
		return $result;
492
	}
493
}
494
495
/**
496
 * This class implements the Ripcord_Transport interface using CURL.
497
 * @package Ripcord
498
 */
499
class Ripcord_Transport_CURL implements Ripcord_Transport
500
{
501
	/**
502
	 * A list of CURL options.
503
	 */
504
	private $options = array();
505
506
	/**
507
	 * A flag that indicates whether or not we can safely pass the previous exception to a new exception.
508
	 */
509
	private $skipPreviousException = false;
0 ignored issues
show
Unused Code introduced by
The property $skipPreviousException is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
510
511
	/**
512
	 * Contains the headers sent by the server.
513
	 */
514
	public $responseHeaders = null;
515
516
	/**
517
	 * This is the constructor for the Ripcord_Transport_CURL class.
518
	 * @param array $curlOptions A list of CURL options.
519
	 */
520
	public function __construct( $curlOptions = null )
521
	{
522
		if ( isset($curlOptions) )
523
		{
524
			$this->options = $curlOptions;
525
		}
526
		$version = explode('.', phpversion() );
527
		if ( ( (0 + $version[0]) == 5) && ( 0 + $version[1]) < 3 ) { // previousException supported in php >= 5.3
528
			$this->_skipPreviousException = true;
0 ignored issues
show
Bug introduced by
The property _skipPreviousException does not seem to exist. Did you mean skipPreviousException?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
529
		}
530
	}
531
532
	/**
533
	 * This method posts the request to the given url
534
	 * @param string $url The url to post to.
535
	 * @param string $request The request to post.
536
	 * @throws Ripcord_TransportException (ripcord::cannotAccessURL) when the given URL cannot be accessed for any reason.
537
	 * @return string The server response
538
	 */
539
	public function post( $url, $request)
540
	{
541
		$curl = curl_init();
542
		$options = (array) $this->options + array(
543
			CURLOPT_RETURNTRANSFER => 1,
544
			CURLOPT_URL            => $url,
545
			CURLOPT_POST           => true,
546
			CURLOPT_POSTFIELDS     => $request,
547
			CURLOPT_HEADER         => true
548
		);
549
		curl_setopt_array( $curl, $options );
550
		$contents = curl_exec( $curl );
551
		$headerSize = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
552
		$this->responseHeaders = substr( $contents, 0, $headerSize );
553
		$contents = substr( $contents, $headerSize );
554
555
		if ( curl_errno( $curl ) )
556
		{
557
			$errorNumber = curl_errno( $curl );
558
			$errorMessage = curl_error( $curl );
559
			curl_close( $curl );
560
			$version = explode('.', phpversion() );
0 ignored issues
show
Unused Code introduced by
$version is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
561
			if (!$this->_skipPreviousException) { // previousException supported in php >= 5.3
0 ignored issues
show
Bug introduced by
The property _skipPreviousException does not seem to exist. Did you mean skipPreviousException?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
562
				$exception = new Ripcord_TransportException( 'Could not access ' . $url
563
					, ripcord::cannotAccessURL
564
					, new Exception( $errorMessage, $errorNumber )
565
				);
566
			} else {
567
				$exception = new Ripcord_TransportException( 'Could not access ' . $url
568
					. ' ( original CURL error: ' . $errorMessage . ' ) ',
569
					ripcord::cannotAccessURL
570
				);
571
			}
572
			throw $exception;
573
		}
574
		curl_close($curl);
575
		return $contents;
576
	}
577
}
578