Completed
Push — master ( 3167db...70e3fd )
by Gaetano
07:00
created

Encoder::decode()   D

Complexity

Conditions 20
Paths 19

Size

Total Lines 80

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 56.6935

Importance

Changes 0
Metric Value
cc 20
nc 19
nop 2
dl 0
loc 80
ccs 28
cts 51
cp 0.549
crap 56.6935
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\XMLParser;
6
7
/**
8
 * A helper class to easily convert between Value objects and php native values
9
 * @todo implement an interface
10
 */
11
class Encoder
12
{
13
    /**
14
     * Takes an xmlrpc value in object format and translates it into native PHP types.
15
     *
16
     * Works with xmlrpc requests objects as input, too.
17
     *
18
     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
19
     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
20
     * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
21
     * This means that the remote communication end can decide which php code will get executed on your server, leaving
22
     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
23
     * that might wreak havoc if instances are built outside an appropriate context).
24
     * Make sure you trust the remote server/client before eanbling this!
25
     *
26
     * @author Dan Libby ([email protected])
27
     *
28
     * @param Value|Request $xmlrpcVal
29
     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
30
     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
31
     *
32
     * @return mixed
33
     */
34 230
    public function decode($xmlrpcVal, $options = array())
35
    {
36 230
        switch ($xmlrpcVal->kindOf()) {
37 230
            case 'scalar':
38 230
                if (in_array('extension_api', $options)) {
39
                    $val = reset($xmlrpcVal->me);
40
                    $typ = key($xmlrpcVal->me);
41
                    switch ($typ) {
42
                        case 'dateTime.iso8601':
43
                            $xmlrpcVal->scalar = $val;
44
                            $xmlrpcVal->type = 'datetime';
45
                            $xmlrpcVal->timestamp = \PhpXmlRpc\Helper\Date::iso8601Decode($val);
46
47
                            return $xmlrpcVal;
48
                        case 'base64':
49
                            $xmlrpcVal->scalar = $val;
50
                            $xmlrpcVal->type = $typ;
51
52
                            return $xmlrpcVal;
53
                        default:
54
                            return $xmlrpcVal->scalarval();
0 ignored issues
show
Bug introduced by
The method scalarval does only exist in PhpXmlRpc\Value, but not in PhpXmlRpc\Request.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
55
                    }
56
                }
57 230
                if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') {
0 ignored issues
show
Bug introduced by
The method scalartyp does only exist in PhpXmlRpc\Value, but not in PhpXmlRpc\Request.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
58
                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
59
                    // safely strings, ints and datetimes, we cater to all 3 cases here
60
                    $out = $xmlrpcVal->scalarval();
61
                    if (is_string($out)) {
62
                        $out = strtotime($out);
63
                    }
64
                    if (is_int($out)) {
65
                        $result = new \DateTime();
66
                        $result->setTimestamp($out);
67
68
                        return $result;
69
                    } elseif (is_a($out, 'DateTimeInterface')) {
70
                        return $out;
71
                    }
72
                }
73
74 230
                return $xmlrpcVal->scalarval();
75 230
            case 'array':
76 95
                $arr = array();
77 95
                foreach($xmlrpcVal as $value) {
0 ignored issues
show
Bug introduced by
The expression $xmlrpcVal of type object<PhpXmlRpc\Value>|object<PhpXmlRpc\Request> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
78 95
                    $arr[] = $this->decode($value, $options);
79
                }
80
81 95
                return $arr;
82 173
            case 'struct':
83
                // If user said so, try to rebuild php objects for specific struct vals.
84
                /// @todo should we raise a warning for class not found?
85
                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
86
                // what we can do?
87 57
                if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != ''
88 38
                    && class_exists($xmlrpcVal->_php_class)
89
                ) {
90 19
                    $obj = @new $xmlrpcVal->_php_class();
91 19
                    foreach ($xmlrpcVal as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $xmlrpcVal of type object<PhpXmlRpc\Value>|object<PhpXmlRpc\Request> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
92 19
                        $obj->$key = $this->decode($value, $options);
93
                    }
94
95 19
                    return $obj;
96
                } else {
97 38
                    $arr = array();
98 38
                    foreach ($xmlrpcVal as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $xmlrpcVal of type object<PhpXmlRpc\Value>|object<PhpXmlRpc\Request> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
99 38
                        $arr[$key] = $this->decode($value, $options);
100
                    }
101
102 38
                    return $arr;
103
                }
104 116
            case 'msg':
105 116
                $paramCount = $xmlrpcVal->getNumParams();
0 ignored issues
show
Bug introduced by
The method getNumParams does only exist in PhpXmlRpc\Request, but not in PhpXmlRpc\Value.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
106 116
                $arr = array();
107 116
                for ($i = 0; $i < $paramCount; $i++) {
108 116
                    $arr[] = $this->decode($xmlrpcVal->getParam($i), $options);
0 ignored issues
show
Bug introduced by
The method getParam does only exist in PhpXmlRpc\Request, but not in PhpXmlRpc\Value.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
109
                }
110
111 116
                return $arr;
112
        }
113
    }
114
115
    /**
116
     * Takes native php types and encodes them into xmlrpc PHP object format.
117
     * It will not re-encode xmlrpc value objects.
118
     *
119
     * Feature creep -- could support more types via optional type argument
120
     * (string => datetime support has been added, ??? => base64 not yet)
121
     *
122
     * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can
123
     * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option
124
     *
125
     * @author Dan Libby ([email protected])
126
     *
127
     * @param mixed $phpVal the value to be converted into an xmlrpc value object
128
     * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
129
     *
130
     * @return \PhpXmlrpc\Value
131
     */
132 217
    public function encode($phpVal, $options = array())
133
    {
134 217
        $type = gettype($phpVal);
135 217
        switch ($type) {
136 217
            case 'string':
137 214
                if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) {
138 1
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime);
139
                } else {
140 214
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString);
141
                }
142 214
                break;
143 103
            case 'integer':
144 98
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt);
145 98
                break;
146 26
            case 'double':
147 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble);
148 1
                break;
149
            // Add support for encoding/decoding of booleans, since they are supported in PHP
150 26
            case 'boolean':
151 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean);
152 1
                break;
153 26
            case 'array':
154
                // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes
155
                // or plain 0..n integer indexed
156
                // A shorter one-liner would be
157
                // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1));
158
                // but execution time skyrockets!
159 23
                $j = 0;
160 23
                $arr = array();
161 23
                $ko = false;
162 23
                foreach ($phpVal as $key => $val) {
163 23
                    $arr[$key] = $this->encode($val, $options);
164 23
                    if (!$ko && $key !== $j) {
165 21
                        $ko = true;
166
                    }
167 23
                    $j++;
168
                }
169 23
                if ($ko) {
170 21
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
0 ignored issues
show
Documentation introduced by
$arr is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
171
                } else {
172 3
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcArray);
0 ignored issues
show
Documentation introduced by
$arr is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
173
                }
174 23
                break;
175 3
            case 'object':
176 1
                if (is_a($phpVal, 'PhpXmlRpc\Value')) {
177 1
                    $xmlrpcVal = $phpVal;
178
                } elseif (is_a($phpVal, 'DateTimeInterface')) {
179
                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcStruct);
180
                } else {
181
                    $arr = array();
182
                    foreach($phpVal as $k => $v) {
183
                        $arr[$k] = $this->encode($v, $options);
184
                    }
185
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
0 ignored issues
show
Documentation introduced by
$arr is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
186
                    if (in_array('encode_php_objs', $options)) {
187
                        // let's save original class name into xmlrpc value:
188
                        // might be useful later on...
189
                        $xmlrpcVal->_php_class = get_class($phpVal);
190
                    }
191
                }
192 1
                break;
193 2
            case 'NULL':
194 2
                if (in_array('extension_api', $options)) {
195
                    $xmlrpcVal = new Value('', Value::$xmlrpcString);
196 2
                } elseif (in_array('null_extension', $options)) {
197
                    $xmlrpcVal = new Value('', Value::$xmlrpcNull);
198
                } else {
199 2
                    $xmlrpcVal = new Value();
200
                }
201 2
                break;
202
            case 'resource':
203
                if (in_array('extension_api', $options)) {
204
                    $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt);
205
                } else {
206
                    $xmlrpcVal = new Value();
207
                }
208
                break;
209
            // catch "user function", "unknown type"
210
            default:
211
                // giancarlo pinerolo <[email protected]>
212
                // it has to return an empty object in case, not a boolean.
213
                $xmlrpcVal = new Value();
214
                break;
215
        }
216
217 217
        return $xmlrpcVal;
218
    }
219
220
    /**
221
     * Convert the xml representation of a method response, method request or single
222
     * xmlrpc value into the appropriate object (a.k.a. deserialize).
223
     *
224
     * Q: is this a good name for this method? It does something quite different from 'decode' after all
225
     * (returning objects vs returns plain php values)...
226
     *
227
     * @param string $xmlVal
228
     * @param array $options
229
     *
230
     * @return mixed false on error, or an instance of either Value, Request or Response
231
     */
232 3
    public function decodeXml($xmlVal, $options = array())
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
233
    {
234
        // 'guestimate' encoding
235 3
        $valEncoding = XMLParser::guessEncoding('', $xmlVal);
236 3 View Code Duplication
        if ($valEncoding != '') {
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...
237
238
            // Since parsing will fail if charset is not specified in the xml prologue,
239
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
240
            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
241
            //if (!is_valid_charset($valEncoding, array('UTF-8'))
242 3
            if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
243
                if ($valEncoding == 'ISO-8859-1') {
244
                    $xmlVal = utf8_encode($xmlVal);
245
                } else {
246
                    if (extension_loaded('mbstring')) {
247
                        $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
248
                    } else {
249
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
250
                    }
251
                }
252
            }
253
        }
254
255
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
256 3 View Code Duplication
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
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...
257
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
258
        } else {
259 3
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
260
        }
261
262 3
        $xmlRpcParser = new XMLParser($options);
263 3
        $xmlRpcParser->parse($xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE);
264
265 3
        if ($xmlRpcParser->_xh['isf'] > 1) {
266
            // test that $xmlrpc->_xh['value'] is an obj, too???
267
268
            Logger::instance()->errorLog($xmlRpcParser->_xh['isf_reason']);
269
270
            return false;
271
        }
272
273 3
        switch ($xmlRpcParser->_xh['rt']) {
274 3
            case 'methodresponse':
275 3
                $v = $xmlRpcParser->_xh['value'];
276 3
                if ($xmlRpcParser->_xh['isf'] == 1) {
277
                    /** @var Value $vc */
278
                    $vc = $v['faultCode'];
279
                    /** @var Value $vs */
280
                    $vs = $v['faultString'];
281
                    $r = new Response(0, $vc->scalarval(), $vs->scalarval());
282
                } else {
283 3
                    $r = new Response($v);
284
                }
285
286 3
                return $r;
287 1
            case 'methodcall':
288 1
                $req = new Request($xmlRpcParser->_xh['method']);
289 1 View Code Duplication
                for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
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...
290 1
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
291
                }
292
293 1
                return $req;
294 1
            case 'value':
295 1
                return $xmlRpcParser->_xh['value'];
296
            default:
297
                return false;
298
        }
299
    }
300
301
}
302