Completed
Push — master ( 3151bf...a071b7 )
by Marco
03:58
created

RpcClient   D

Complexity

Total Complexity 88

Size/Duplication

Total Lines 720
Duplicated Lines 6.67 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 60.29%

Importance

Changes 0
Metric Value
wmc 88
lcom 1
cbo 6
dl 48
loc 720
ccs 126
cts 209
cp 0.6029
rs 4.4444
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 3
A setProtocol() 0 11 2
A setEncryption() 0 9 2
A setEncoding() 0 7 1
A setXmlEncoder() 0 9 2
A setValueType() 0 9 3
A addRequest() 0 15 4
A setAutoclean() 0 7 1
A getTransport() 0 5 1
C send() 10 51 11
A cleanRequests() 0 9 1
C jsonCall() 0 39 8
C jsonMulticall() 0 55 11
C xmlCall() 19 68 12
C xmlMulticall() 19 66 10
B performCall() 0 31 5
A phpXmlrpcAvailable() 0 5 2
B splitMulticallXmlRequests() 0 24 4
B composeJsonRequest() 0 25 3
A checkEncryptedResponseConsistency() 0 5 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RpcClient 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 RpcClient, and based on these observations, apply Extract Interface, too.

1
<?php namespace Comodojo\RpcClient;
2
3
use \Comodojo\Exception\RpcException;
4
use \Comodojo\Exception\HttpException;
5
use \Comodojo\Exception\XmlrpcException;
6
use \Exception;
7
use \Comodojo\Httprequest\Httprequest;
8
use \Comodojo\Xmlrpc\XmlrpcEncoder;
9
use \Comodojo\Xmlrpc\XmlrpcDecoder;
10
use \Crypt_AES;
11
12
/** 
13
 * Comodojo RPC client. It's able to talk in XML and JSON (2.0).
14
 *
15
 * It optionally supports a not standard encrypted transport
16
 * 
17
 * @package     Comodojo Spare Parts
18
 * @author      Marco Giovinazzi <[email protected]>
19
 * @license     MIT
20
 *
21
 * LICENSE:
22
 * 
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
 * THE SOFTWARE.
30
 */
31
32
class RpcClient {
33
    
34
    /**
35
     * Default disabled: native funcs are faster but are not compatible with base64, datetime
36
     * and cdata values (as implemented here)
37
     *
38
     * @var bool
39
     */
40
    static private $useNativeEncoder = false;
41
42
    /**
43
     * Enable comodojo encrypted transport
44
     *
45
     * @var mixed
46
     */
47
    private $encrypt = false;
48
    
49
    /**
50
     * RPC protocol
51
     *
52
     * @var string
53
     */
54
    private $protocol = 'XML';
55
    
56
    /**
57
     * Characters encoding
58
     *
59
     * @var string
60
     */
61
    private $encoding = 'utf-8';
62
63
    /**
64
     * Autoclean requests
65
     *
66
     * @var string
67
     */
68
    private $autoclean = true;
69
70
    /**
71
     * Supported RPC protocols
72
     *
73
     * @var string
74
     */
75
    private $supported_protocols = array("XML", "JSON");
76
77
    // internals
78
79
    private $sender = null;
80
81
    private $requests = array();
82
83
    private $special_types = array();
84
    
85
    /**
86
     * Class constructor
87
     *
88
     * @param   string  $server  Remote RPC server address
89
     *
90
     * @throws \Comodojo\Exception\HttpException
91
     */
92 51
    public function __construct($server) {
93
94 51
        if ( empty($server) ) throw new Exception("Invalid RPC server address");
95
96
        try {
97
98 51
            $this->sender = new Httprequest($server);
99
            
100 51
            $this->sender->setHttpMethod("POST");
101
102 51
        } catch (HttpException $he) {
103
104
            throw $he;
105
106
        }
107
108 51
    }
109
110
    /**
111
     * Set RPC protocol
112
     *
113
     * @param   string  $protocol RPC protocol
114
     *
115
     * @return  \Comodojo\RpcClient\RpcClient
116
     * 
117
     * @throws \Exception
118
     */
119 12
    final public function setProtocol($protocol) {
120
121 12
        $proto = strtoupper($protocol);
122
123 12
        if ( !in_array($proto, $this->supported_protocols) ) throw new Exception("Invalid RPC protocol");
124
125 12
        $this->protocol = $proto;
126
127 12
        return $this;
128
129
    }
130
131
    /**
132
     * Set encryption key; this will enable the NOT-STANDARD payload encryption
133
     *
134
     * @param   string  $key Encryption key
135
     *
136
     * @return  \Comodojo\RpcClient\RpcClient
137
     * 
138
     * @throws \Exception
139
     */
140
    final public function setEncryption($key) {
141
142
        if ( empty($key) ) throw new Exception("Shared key cannot be empty");
143
144
        $this->encrypt = $key;
145
146
        return $this;
147
148
    }
149
150
    /**
151
     * Set encoding (default to utf-8)
152
     *
153
     * @param   string  $encoding Characters encoding
154
     *
155
     * @return  \Comodojo\RpcClient\RpcClient
156
     */
157
    final public function setEncoding($encoding) {
158
159
        $this->encoding = $encoding;
160
161
        return $this;
162
163
    }
164
    
165
    /**
166
     * Set the XML encoder
167
     * 
168
     * If true, the comodojo xmlrpc encoder will be used (default). Otherwise
169
     * message will be encoded using PHP XML-RPC Functions.
170
     * 
171
     * WARNING: using PHP XML-RPC Functions does not support special value
172
     * types support!
173
     *
174
     * @param   bool  $mode
175
     *
176
     * @return  \Comodojo\RpcClient\RpcClient
177
     */
178
    final public function setXmlEncoder($mode = true) {
179
        
180
        if ( $mode === false ) $this->useNativeEncoder = true;
181
        
182
        else $this->useNativeEncoder = false;
183
        
184
        return $this;
185
        
186
    }
187
188
    /**
189
     * Set special type for a given values (referenced)
190
     * 
191
     * @param   mixed  $value The given value (referenced)
192
     * @param   string $type  The value type (base64, datetime or cdata)
193
     *
194
     * @return  \Comodojo\RpcClient\RpcClient
195
     * 
196
     * @throws  \Exception
197
     */
198 6
    final public function setValueType(&$value, $type) {
199
200 6
        if ( empty($value) OR !in_array(strtolower($type), array("base64", "datetime", "cdata")) ) throw new Exception("Invalid value type");
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
201
202 6
        $this->special_types[$value] = strtolower($type);
203
204 6
        return $this;
205
206
    }
207
208
    /**
209
     * Add request in queue
210
     * 
211
     * @param   string  $method      RPC method
212
     * @param   array   $parameters  Request parameters
213
     * @param   mixed   $id          Id (only for JSON RPC)
214
     *
215
     * @return  \Comodojo\RpcClient\RpcClient
216
     * 
217
     * @throws  \Exception
218
     */
219 51
    final public function addRequest($method, $parameters = array(), $id = true) {
220
221 51
        if ( empty($method) OR !is_scalar($method) ) throw new Exception("Invalid method (not scalar or empty)");
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
222
        
223 51
        if ( !is_array($parameters) ) throw new Exception("Bad parameters (not array)");
224
225 51
        array_push($this->requests, array(
226 51
            "METHOD"    =>  $method,
227 51
            "PARAMETERS"=>  $parameters,
228
            "ID"        =>  $id
229 51
        ));
230
231 51
        return $this;
232
233
    }
234
235
    /**
236
     * Set autoclean on/off
237
     * 
238
     * @param   bool   $mode  If true, requests will be removed from queue at each send()
239
     *
240
     * @return  \Comodojo\RpcClient\RpcClient
241
     */
242
    final public function setAutoclean($mode = true) {
243
244
        $this->autoclean = filter_var($mode, FILTER_VALIDATE_BOOLEAN);
245
246
        return $this;
247
248
    }
249
250
    /**
251
     * Get the transport layer
252
     * 
253
     * This method will return the Httprequest object in order to customize transport
254
     * options before sending request(s)
255
     * 
256
     * @return  \Comodojo\Httprequest\Httprequest
257
     */
258 51
    final public function getTransport() {
259
        
260 51
        return $this->sender;
261
        
262
    }
263
264
    /**
265
     * Send request(s) to server
266
     *
267
     * @return mixed
268
     * 
269
     * @throws \Comodojo\Exception\RpcException
270
     * @throws \Comodojo\Exception\HttpException
271
     * @throws \Comodojo\Exception\XmlrpcException
272
     * @throws \Exception
273
     */
274 51
    public function send() {
275
276 51
        if ( sizeof($this->requests) == 0 ) throw new Exception("Empty request, cannot perform call");
277
278
        try {
279
280 51
            switch ( $this->protocol ) {
281
282 51 View Code Duplication
                case 'XML':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
283
                    
284 39
                    $response = sizeof($this->requests) == 1 ? $this->xmlCall($this->requests[0]) : $this->xmlMulticall($this->requests);
285
                
286 39
                    break;
287
                    
288 12 View Code Duplication
                case 'JSON':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
289
290 12
                    $response = sizeof($this->requests) == 1 ? $this->jsonCall($this->requests[0]) : $this->jsonMulticall($this->requests);
291
292 12
                    break;
293
                
294
                default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
295
296
                    throw new Exception("Invalid RPC transport protocol");
297
298
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
299
300 51
            }
301
302 51
        } catch (HttpException $he) {
303
304
            throw $he;
305
306
        } catch (RpcException $re) {
307
308
            throw $re;
309
310
        } catch (XmlrpcException $xe) {
311
312
            throw $xe;
313
314
        } catch (Exception $e) {
315
316
            throw $e;
317
318
        }
319
        
320 51
        if ( $this->autoclean ) $this->cleanRequests();
321
322 51
        return $response;
323
324
    }
325
326
    /**
327
     * Cleanup requests
328
     * 
329
     * @return  \Comodojo\RpcClient\RpcClient
330
     */
331 51
    final public function cleanRequests() {
332
333 51
        $this->requests = array();
334
335 51
        $this->special_types = array();
336
337 51
        return $this;
338
339
    }
340
341
    /**
342
     * Perform a json call
343
     *
344
     * @param   array   $request
345
     * 
346
     * @return  mixed
347
     *
348
     * @throws \Comodojo\Exception\RpcException
349
     * @throws \Comodojo\Exception\HttpException
350
     * @throws \Exception
351
     */
352 9
    private function jsonCall($request) {
353
354 9
        list($json_request, $id) = self::composeJsonRequest($request);
355
356
        try {
357
            
358 9
            $received = $this->performCall(json_encode($json_request), 'application/json');
359
360 9
            if ( $id !== null ) {
361
362 6
                $response = json_decode($received, true);
363
364 6
                if ( is_null($response) ) throw new Exception("Incomprehensible or empty response");
365
366 6
                else if ( isset($response["error"]) ) throw new RpcException($response["error"]["message"], $response["error"]["code"]);
367
                
368 6
                else if ( $response["id"] != $id ) throw new Exception("Invalid response ID received");
369
370 6
                else $return = $response["result"];
371
372 9
            } else $return = true;
373
374 9
        } catch (HttpException $he) {
375
376
            throw $he;
377
378
        } catch (RpcException $re) {
379
380
            throw $re;
381
382
        } catch (Exception $e) {
383
384
            throw $e;
385
386
        }
387
388 9
        return $return;
389
390
    }
391
392
    /**
393
     * Perform a json multicall
394
     *
395
     * @param   array   $requests
396
     * 
397
     * @return  array
398
     *
399
     * @throws \Comodojo\Exception\RpcException
400
     * @throws \Comodojo\Exception\HttpException
401
     * @throws \Exception
402
     */
403 3
    private function jsonMulticall($requests) {
404
405 3
        $expected_ids = array();
406
407 3
        $batch_request = array();
408
409 3
        $batch_response = array();
410
411 3
        foreach ( $requests as $request ) {
412
            
413 3
            list($json_request, $id) = self::composeJsonRequest($request);
414
415 3
            if ( $id !== null ) $expected_ids[] = $id;
416
417 3
            $batch_request[] = $json_request;
418
419 3
        }
420
421
        try {
422
            
423 3
            $received = $this->performCall(json_encode($batch_request), 'application/json');
424
425 3
        } catch (HttpException $he) {
426
427
            throw $he;
428
429
        } catch (Exception $e) {
430
431
            throw $e;
432
433
        }
434
435 3
        if ( !empty($expected_ids) ) {
436
437 3
            $response = json_decode($received, true);
438
439 3
            if ( is_null($response) ) throw new Exception("Incomprehensible or empty response");
440
441 3
            foreach ( $expected_ids as $key => $id ) {
442
                
443 3
                if ( !isset($response[$key]) ) $batch_response[$key] = array("error" => array("code" => null, "message" => "Empty response"));
444
445 3
                else if ( isset($response[$key]["error"]) ) $batch_response[$key] = array("error" => $response["error"]);
446
447 3
                else if ( $response[$key]["id"] != $id ) $batch_response[$key] = array("error" => array("code" => null, "message" => "Invalid response ID received"));
448
449 3
                else $batch_response[$key] = array("result" => $response[$key]["result"]);
450
451 3
            }
452
453 3
        } else $batch_response = true;
454
455 3
        return $batch_response;
456
457
    }
458
459
    /**
460
     * Perform an xml call
461
     *
462
     * @param   array   $request
463
     * 
464
     * @return  mixed
465
     *
466
     * @throws \Comodojo\Exception\RpcException
467
     * @throws \Comodojo\Exception\HttpException
468
     * @throws \Comodojo\Exception\XmlrpcException
469
     * @throws \Exception
470
     */
471 36
    private function xmlCall($request) {
472
473
        try {
474
        
475 36
            if ( self::phpXmlrpcAvailable() ) {
476
477
                foreach ( $this->special_types as $key => $value ) xmlrpc_set_type($key, $value);
478
479
                $real_request = xmlrpc_encode_request($request["METHOD"], $request["PARAMETERS"], array(
480
                    'encoding' => $this->encoding,
481
                    'verbosity'=> 'no_white_space',
482
                    'version'  => 'xmlrpc'
483
                ));
484
485
            } else {
486
487 36
                $encoder = new XmlrpcEncoder();
488
489 36
                foreach ( $this->special_types as $key => $value ) $encoder->setValueType($key, $value); 
490
491 36
                $real_request = $encoder->setEncoding($this->encoding)->encodeCall($request["METHOD"], $request["PARAMETERS"]);
492
493
            }
494
            
495 36
            $received = $this->performCall($real_request, 'text/xml');
496
497 36 View Code Duplication
            if ( self::phpXmlrpcAvailable() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
498
499
                $decoded = xmlrpc_decode($received);
500
501
                if ( is_array($decoded) && xmlrpc_is_fault($decoded) ) throw new RpcException($decoded['faultString'], $decoded['faultCode']);
502
503
                $return = $decoded;
504
505
            } else {
506
507 36
                $decoder = new XmlrpcDecoder();
508
509 36
                $decoded = $decoder->decodeResponse($received);
510
511 36
                if ( $decoder->isFault() ) throw new RpcException($decoded['faultString'], $decoded['faultCode']);
512
513 36
                $return = $decoded;
514
515
            }
516
517
518 36
        } catch (RpcException $re) {
519
520
            throw $re;
521
522
        } catch (HttpException $he) {
523
524
            throw $he;
525
526
        } catch (XmlrpcException $xe) {
527
528
            throw $xe;
529
530
        } catch (Exception $e) {
531
532
            throw $e;
533
534
        }
535
536 36
        return $return;
537
538
    }
539
540
    /**
541
     * Perform an xml multicall
542
     *
543
     * @param   array   $requests
544
     * 
545
     * @return  array
546
     *
547
     * @throws \Comodojo\Exception\RpcException
548
     * @throws \Comodojo\Exception\HttpException
549
     * @throws \Comodojo\Exception\XmlrpcException
550
     * @throws \Exception
551
     */
552 3
    private function xmlMulticall($requests) {
553
554 3
        $requests = self::splitMulticallXmlRequests($requests);
555
556
        try {
557
        
558 3
            if ( self::phpXmlrpcAvailable() ) {
559
560
                $request = xmlrpc_encode_request("system.multicall", $requests, array(
561
                    'encoding' => $this->encoding,
562
                    'verbosity'=> 'no_white_space',
563
                    'version'  => 'xmlrpc'
564
                ));
565
566
            } else {
567
568 3
                $encoder = new XmlrpcEncoder();
569
570 3
                $request = $encoder->setEncoding($this->encoding)->encodeMulticall($requests);
571
572
            }
573
574 3
            $received = $this->performCall($request, 'text/xml');
575
576 3 View Code Duplication
            if ( self::phpXmlrpcAvailable() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
577
578
                $decoded = xmlrpc_decode($received);
579
580
                if ( is_array($decoded) && xmlrpc_is_fault($decoded) ) throw new RpcException($decoded['faultString'], $decoded['faultCode']);
581
582
                $return = $decoded;
583
584
            } else {
585
586 3
                $decoder = new XmlrpcDecoder();
587
588 3
                $decoded = $decoder->decodeResponse($received);
589
590 3
                if ( $decoder->isFault() ) throw new RpcException($decoded['faultString'], $decoded['faultCode']);
591
592 3
                $return = $decoded;
593
594
            }
595
596
597 3
        } catch (HttpException $he) {
598
599
            throw $he;
600
601
        } catch (RpcException $re) {
602
603
            throw $re;
604
605
        } catch (XmlrpcException $xe) {
606
607
            throw $xe;
608
609
        } catch (Exception $e) {
610
611
            throw $e;
612
613
        }
614
615 3
        return $return;
616
617
    }
618
619
    /**
620
     * Send pre-econded request to server
621
     *
622
     * @param   string   $data
623
     * @param   string   $content_type
624
     * 
625
     * @return  string
626
     *
627
     * @throws \Comodojo\Exception\RpcException
628
     * @throws \Comodojo\Exception\HttpException
629
     */
630 51
    private function performCall($data, $content_type) {
631
632 51
        if ( $this->encrypt !== false ) {
633
634
            $aes = new Crypt_AES();
635
636
            $aes->setKey($this->encrypt);
637
638
            $data = 'comodojo_encrypted_request-'.base64_encode($aes->encrypt($data));
639
640
        }
641
    
642
        try {
643
644 51
            $response = $this->sender->setContentType($content_type)->send($data);
645
646 51
        } catch (HttpException $he) {
647
648
            throw $he;
649
650
        }
651
652 51
        if ( $this->encrypt !== false ) {
653
            
654
            if ( self::checkEncryptedResponseConsistency($response) === false ) throw new RpcException("Inconsistent encrypted response received");
655
        
656
            return $aes->decrypt(base64_decode(substr($response, 28)));
0 ignored issues
show
Bug introduced by
The variable $aes does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
657
           
658 51
        } else return $response;
659
660
    }
661
662
    /**
663
     * Check native encoder availability
664
     *
665
     * @return  bool
666
     */
667 39
    private static function phpXmlrpcAvailable() {
668
669 39
        return (function_exists('xmlrpc_encode_request') AND self::$useNativeEncoder);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
670
671
    }
672
673
    /**
674
     * Split multicall xml requests
675
     *
676
     * @param   array    $requests
677
     * 
678
     * @return  array
679
     */
680 3
    private static function splitMulticallXmlRequests($requests) {
681
682 3
        $return = array();
683
684 3
        if ( self::phpXmlrpcAvailable() ) {
685
686
            foreach ( $requests as $request ) {
687
                
688
                array_push($return, array(
689
                    "methodName"    =>  $request["METHOD"],
690
                    "params"        =>  $request["PARAMETERS"]
691
                ));
692
693
            }
694
695
        } else {
696
697 3
            foreach ( $requests as $request ) $return[] = array($request["METHOD"], $request["PARAMETERS"]);
698
699
        }
700
    
701 3
        return $return;
702
703
    }
704
705
    /**
706
     * Compose a json request
707
     *
708
     * @param   array    $request
709
     * 
710
     * @return  array
711
     */
712 12
    private static function composeJsonRequest($request) {
713
714
        $return = array(
715 12
            "jsonrpc"   =>  "2.0",
716 12
            "method"    =>  $request["METHOD"],
717 12
            "params"    =>  $request["PARAMETERS"]
718 12
        );
719
720 12
        if ( $request["ID"] === true ) {
721
722 9
            $return["id"] = mt_rand();
723
724 9
            $id = $return["id"];
725
726 12
        } else if ( is_scalar($request["ID"]) ) {
727
728
            $return["id"] = $request["ID"];
729
730
            $id = $return["id"];
731
732 3
        } else $id = null;
733
734 12
        return array($return, $id);
735
736
    }
737
    
738
    /**
739
     * Check if an encrypted envelop is consisent or not
740
     *
741
     * @param   string    $data
742
     * 
743
     * @return  bool
744
     */
745
    private static function checkEncryptedResponseConsistency($data) {
746
        
747
        return substr($data, 0, 27) == 'comodojo_encrypted_response' ? true : false;
748
        
749
    }
750
    
751
}
752