Completed
Push — master ( 6be6c0...cc60cf )
by Gaetano
03:16
created

Encoder   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 310
Duplicated Lines 8.71 %

Coupling/Cohesion

Components 0
Dependencies 6

Test Coverage

Coverage 41.29%

Importance

Changes 0
Metric Value
dl 27
loc 310
ccs 64
cts 155
cp 0.4129
rs 5.5199
c 0
b 0
f 0
wmc 56
lcom 0
cbo 6

3 Methods

Rating   Name   Duplication   Size   Complexity  
D decode() 0 81 20
D encode() 0 90 22
C decodeXml() 27 84 14

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 Encoder 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 Encoder, and based on these observations, apply Extract Interface, too.

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