Completed
Push — master ( 820f99...3ba560 )
by Gaetano
07:24
created

Encoder::encode()   D

Complexity

Conditions 22
Paths 17

Size

Total Lines 87

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 24.6117

Importance

Changes 0
Metric Value
cc 22
nc 17
nop 2
dl 0
loc 87
ccs 47
cts 57
cp 0.8246
crap 24.6117
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\Logger;
6
use PhpXmlRpc\Helper\XMLParser;
7
8
/**
9
 * A helper class to easily convert between Value objects and php native values
10
 * @todo implement an interface
11
 */
12
class Encoder
13
{
14
    /**
15
     * Takes an xmlrpc value in object format and translates it into native PHP types.
16
     *
17
     * Works with xmlrpc requests objects as input, too.
18
     *
19
     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
20
     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
21
     * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
22
     * This means that the remote communication end can decide which php code will get executed on your server, leaving
23
     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
24
     * that might wreak havoc if instances are built outside an appropriate context).
25
     * Make sure you trust the remote server/client before eanbling this!
26
     *
27
     * @author Dan Libby ([email protected])
28
     *
29
     * @param Value|Request $xmlrpcVal
30
     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
31
     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
32
     *
33
     * @return mixed
34
     */
35 231
    public function decode($xmlrpcVal, $options = array())
36
    {
37 231
        switch ($xmlrpcVal->kindOf()) {
38 231
            case 'scalar':
39 231
                if (in_array('extension_api', $options)) {
40
                    $val = reset($xmlrpcVal->me);
41
                    $typ = key($xmlrpcVal->me);
42
                    switch ($typ) {
43
                        case 'dateTime.iso8601':
44
                            $xmlrpcVal->scalar = $val;
45
                            $xmlrpcVal->type = 'datetime';
46
                            $xmlrpcVal->timestamp = \PhpXmlRpc\Helper\Date::iso8601Decode($val);
47
48
                            return $xmlrpcVal;
49
                        case 'base64':
50
                            $xmlrpcVal->scalar = $val;
51
                            $xmlrpcVal->type = $typ;
52
53
                            return $xmlrpcVal;
54
                        default:
55
                            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...
56
                    }
57
                }
58 231
                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...
59
                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
60
                    // safely strings, ints and datetimes, we cater to all 3 cases here
61
                    $out = $xmlrpcVal->scalarval();
62
                    if (is_string($out)) {
63
                        $out = strtotime($out);
64
                    }
65
                    if (is_int($out)) {
66
                        $result = new \DateTime();
67
                        $result->setTimestamp($out);
68
69
                        return $result;
70
                    } elseif (is_a($out, 'DateTimeInterface')) {
71
                        return $out;
72
                    }
73
                }
74
75 231
                return $xmlrpcVal->scalarval();
76 231
            case 'array':
77 96
                $arr = array();
78 96
                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...
79 96
                    $arr[] = $this->decode($value, $options);
80
                }
81
82 96
                return $arr;
83 174
            case 'struct':
84
                // If user said so, try to rebuild php objects for specific struct vals.
85
                /// @todo should we raise a warning for class not found?
86
                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
87
                // what we can do?
88 58
                if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != ''
89 39
                    && class_exists($xmlrpcVal->_php_class)
90
                ) {
91 20
                    $obj = @new $xmlrpcVal->_php_class();
92 20
                    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...
93 20
                        $obj->$key = $this->decode($value, $options);
94
                    }
95
96 20
                    return $obj;
97
                } else {
98 39
                    $arr = array();
99 39
                    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...
100 39
                        $arr[$key] = $this->decode($value, $options);
101
                    }
102
103 39
                    return $arr;
104
                }
105 117
            case 'msg':
106 117
                $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...
107 117
                $arr = array();
108 117
                for ($i = 0; $i < $paramCount; $i++) {
109 117
                    $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...
110
                }
111
112 117
                return $arr;
113
        }
114
    }
115
116
    /**
117
     * Takes native php types and encodes them into xmlrpc PHP object format.
118
     * It will not re-encode xmlrpc value objects.
119
     *
120
     * Feature creep -- could support more types via optional type argument
121
     * (string => datetime support has been added, ??? => base64 not yet)
122
     *
123
     * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can
124
     * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option
125
     *
126
     * @author Dan Libby ([email protected])
127
     *
128
     * @param mixed $phpVal the value to be converted into an xmlrpc value object
129
     * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
130
     *
131
     * @return \PhpXmlrpc\Value
132
     */
133 217
    public function encode($phpVal, $options = array())
134
    {
135 217
        $type = gettype($phpVal);
136 217
        switch ($type) {
137 217
            case 'string':
138 214
                if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) {
139 1
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime);
140
                } else {
141 214
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString);
142
                }
143 214
                break;
144 103
            case 'integer':
145 98
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt);
146 98
                break;
147 27
            case 'double':
148 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble);
149 1
                break;
150
            // Add support for encoding/decoding of booleans, since they are supported in PHP
151 27
            case 'boolean':
152 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean);
153 1
                break;
154 27
            case 'array':
155
                // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes
156
                // or plain 0..n integer indexed
157
                // A shorter one-liner would be
158
                // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1));
159
                // but execution time skyrockets!
160 24
                $j = 0;
161 24
                $arr = array();
162 24
                $ko = false;
163 24
                foreach ($phpVal as $key => $val) {
164 24
                    $arr[$key] = $this->encode($val, $options);
165 24
                    if (!$ko && $key !== $j) {
166 22
                        $ko = true;
167
                    }
168 24
                    $j++;
169
                }
170 24
                if ($ko) {
171 22
                    $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...
172
                } else {
173 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...
174
                }
175 24
                break;
176 4
            case 'object':
177 2
                if (is_a($phpVal, 'PhpXmlRpc\Value')) {
178 1
                    $xmlrpcVal = $phpVal;
179 1
                } elseif (is_a($phpVal, 'DateTimeInterface')) {
180
                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcStruct);
181
                } else {
182 1
                    $arr = array();
183 1
                    foreach($phpVal as $k => $v) {
184 1
                        $arr[$k] = $this->encode($v, $options);
185
                    }
186 1
                    $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...
187 1
                    if (in_array('encode_php_objs', $options)) {
188
                        // let's save original class name into xmlrpc value:
189
                        // might be useful later on...
190 1
                        $xmlrpcVal->_php_class = get_class($phpVal);
191
                    }
192
                }
193 2
                break;
194 2
            case 'NULL':
195 2
                if (in_array('extension_api', $options)) {
196
                    $xmlrpcVal = new Value('', Value::$xmlrpcString);
197 2
                } elseif (in_array('null_extension', $options)) {
198
                    $xmlrpcVal = new Value('', Value::$xmlrpcNull);
199
                } else {
200 2
                    $xmlrpcVal = new Value();
201
                }
202 2
                break;
203
            case 'resource':
204
                if (in_array('extension_api', $options)) {
205
                    $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt);
206
                } else {
207
                    $xmlrpcVal = new Value();
208
                }
209
                break;
210
            // catch "user function", "unknown type"
211
            default:
212
                // giancarlo pinerolo <[email protected]>
213
                // it has to return an empty object in case, not a boolean.
214
                $xmlrpcVal = new Value();
215
                break;
216
        }
217
218 217
        return $xmlrpcVal;
219
    }
220
221
    /**
222
     * Convert the xml representation of a method response, method request or single
223
     * xmlrpc value into the appropriate object (a.k.a. deserialize).
224
     *
225
     * Q: is this a good name for this method? It does something quite different from 'decode' after all
226
     * (returning objects vs returns plain php values)...
227
     *
228
     * @param string $xmlVal
229
     * @param array $options
230
     *
231
     * @return mixed false on error, or an instance of either Value, Request or Response
232
     */
233 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...
234
    {
235
        // 'guestimate' encoding
236 3
        $valEncoding = XMLParser::guessEncoding('', $xmlVal);
237 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...
238
239
            // Since parsing will fail if charset is not specified in the xml prologue,
240
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
241
            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
242
            //if (!is_valid_charset($valEncoding, array('UTF-8'))
243 3
            if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
244
                if ($valEncoding == 'ISO-8859-1') {
245
                    $xmlVal = utf8_encode($xmlVal);
246
                } else {
247
                    if (extension_loaded('mbstring')) {
248
                        $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
249
                    } else {
250
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
251
                    }
252
                }
253
            }
254
        }
255
256
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
257 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...
258
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
259
        } else {
260 3
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
261
        }
262
263 3
        $xmlRpcParser = new XMLParser($options);
264 3
        $xmlRpcParser->parse($xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE);
265
266 3
        if ($xmlRpcParser->_xh['isf'] > 1) {
267
            // test that $xmlrpc->_xh['value'] is an obj, too???
268
269
            Logger::instance()->errorLog($xmlRpcParser->_xh['isf_reason']);
270
271
            return false;
272
        }
273
274 3
        switch ($xmlRpcParser->_xh['rt']) {
275 3
            case 'methodresponse':
276 3
                $v = $xmlRpcParser->_xh['value'];
277 3
                if ($xmlRpcParser->_xh['isf'] == 1) {
278
                    /** @var Value $vc */
279
                    $vc = $v['faultCode'];
280
                    /** @var Value $vs */
281
                    $vs = $v['faultString'];
282
                    $r = new Response(0, $vc->scalarval(), $vs->scalarval());
283
                } else {
284 3
                    $r = new Response($v);
285
                }
286
287 3
                return $r;
288 1
            case 'methodcall':
289 1
                $req = new Request($xmlRpcParser->_xh['method']);
290 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...
291 1
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
292
                }
293
294 1
                return $req;
295 1
            case 'value':
296 1
                return $xmlRpcParser->_xh['value'];
297
            default:
298
                return false;
299
        }
300
    }
301
302
}
303