Passed
Push — master ( ae5a85...67ed62 )
by Gaetano
24:17
created

Value   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 588
Duplicated Lines 0 %

Test Coverage

Coverage 61.83%

Importance

Changes 11
Bugs 4 Features 0
Metric Value
eloc 265
c 11
b 4
f 0
dl 0
loc 588
ccs 149
cts 241
cp 0.6183
rs 2
wmc 101

22 Methods

Rating   Name   Duplication   Size   Complexity  
A structmemexists() 0 5 1
A structmem() 0 5 1
A serialize() 0 6 1
A structreset() 0 5 1
D serializedata() 0 91 22
B addScalar() 0 41 11
A offsetExists() 0 11 4
A getIterator() 0 10 4
B offsetSet() 0 37 9
A arraysize() 0 5 1
A kindOf() 0 11 4
A addArray() 0 15 3
A offsetUnset() 0 13 4
A scalartyp() 0 9 2
A structsize() 0 5 1
C __construct() 0 32 15
A scalarval() 0 5 1
A addStruct() 0 15 3
B offsetGet() 0 14 7
A arraymem() 0 5 1
A count() 0 11 4
A structeach() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Value 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.

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

1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Helper\Logger;
7
8
/**
9
 * This class enables the creation of values for XML-RPC, by encapsulating plain php values.
10
 */
11
class Value implements \Countable, \IteratorAggregate, \ArrayAccess
12
{
13
    public static $xmlrpcI4 = "i4";
14
    public static $xmlrpcI8 = "i8";
15
    public static $xmlrpcInt = "int";
16
    public static $xmlrpcBoolean = "boolean";
17
    public static $xmlrpcDouble = "double";
18
    public static $xmlrpcString = "string";
19
    public static $xmlrpcDateTime = "dateTime.iso8601";
20
    public static $xmlrpcBase64 = "base64";
21
    public static $xmlrpcArray = "array";
22
    public static $xmlrpcStruct = "struct";
23
    public static $xmlrpcValue = "undefined";
24
    public static $xmlrpcNull = "null";
25
26
    public static $xmlrpcTypes = array(
27
        "i4" => 1,
28
        "i8" => 1,
29
        "int" => 1,
30
        "boolean" => 1,
31
        "double" => 1,
32
        "string" => 1,
33
        "dateTime.iso8601" => 1,
34
        "base64" => 1,
35
        "array" => 2,
36
        "struct" => 3,
37
        "null" => 1,
38
    );
39
40
    /// @todo: do these need to be public?
41
    /** @var Value[]|mixed */
42
    public $me = array();
43
    /** @var int $mytype */
44
    public $mytype = 0;
45
    /** @var string|null $_php_class */
46
    public $_php_class = null;
47
48
    /**
49
     * Build an xmlrpc value.
50
     *
51
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
52
     *
53
     * @param mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
54
     * @param string $type any valid xmlrpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
55
     *                     base64, array, struct, null.
56
     *                     If null, 'string' is assumed.
57
     *                     You should refer to http://www.xmlrpc.com/spec for more information on what each of these mean.
58
     */
59 630
    public function __construct($val = -1, $type = '')
60
    {
61
        // optimization creep - do not call addXX, do it all inline.
62
        // downside: booleans will not be coerced anymore
63 630
        if ($val !== -1 || $type != '') {
64 629
            switch ($type) {
65 629
                case '':
66 103
                    $this->mytype = 1;
67 103
                    $this->me['string'] = $val;
68 103
                    break;
69 629
                case 'i4':
70 629
                case 'i8':
71 629
                case 'int':
72 589
                case 'double':
73 570
                case 'string':
74 401
                case 'boolean':
75 382
                case 'dateTime.iso8601':
76 363
                case 'base64':
77 344
                case 'null':
78 606
                    $this->mytype = 1;
79 606
                    $this->me[$type] = $val;
80 606
                    break;
81 343
                case 'array':
82 215
                    $this->mytype = 2;
83 215
                    $this->me['array'] = $val;
84 215
                    break;
85 244
                case 'struct':
86 244
                    $this->mytype = 3;
87 244
                    $this->me['struct'] = $val;
88 244
                    break;
89
                default:
90
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
91
            }
92
        }
93 630
    }
94
95
    /**
96
     * Add a single php value to an xmlrpc value.
97
     *
98
     * If the xmlrpc value is an array, the php value is added as its last element.
99
     * If the xmlrpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
100
     * Fails if the xmlrpc value is not an array and already initialized.
101
     *
102
     * @param mixed $val
103
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
104
     *
105
     * @return int 1 or 0 on failure
106
     */
107 1
    public function addScalar($val, $type = 'string')
108
    {
109 1
        $typeOf = null;
110 1
        if (isset(static::$xmlrpcTypes[$type])) {
111 1
            $typeOf = static::$xmlrpcTypes[$type];
112
        }
113
114 1
        if ($typeOf !== 1) {
115
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
116
            return 0;
117
        }
118
119
        // coerce booleans into correct values
120
        // NB: we should either do it for datetimes, integers, i8 and doubles, too,
121
        // or just plain remove this check, implemented on booleans only...
122 1
        if ($type == static::$xmlrpcBoolean) {
123
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
124
                $val = true;
125
            } else {
126
                $val = false;
127
            }
128
        }
129
130 1
        switch ($this->mytype) {
131 1
            case 1:
132
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
133
                return 0;
134 1
            case 3:
135 1
                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
136 1
                return 0;
137
            case 2:
138
                // we're adding a scalar value to an array here
139
                $this->me['array'][] = new Value($val, $type);
140
141
                return 1;
142
            default:
143
                // a scalar, so set the value and remember we're scalar
144
                $this->me[$type] = $val;
145
                $this->mytype = $typeOf;
146
147
                return 1;
148
        }
149
    }
150
151
    /**
152
     * Add an array of xmlrpc value objects to an xmlrpc value.
153
     *
154
     * If the xmlrpc value is an array, the elements are appended to the existing ones.
155
     * If the xmlrpc value is empty (uninitialized), this method makes it an array value, and sets that value.
156
     * Fails otherwise.
157
     *
158
     * @param Value[] $values
159
     *
160
     * @return int 1 or 0 on failure
161
     *
162
     * @todo add some checking for $values to be an array of xmlrpc values?
163
     */
164 1
    public function addArray($values)
165
    {
166 1
        if ($this->mytype == 0) {
167
            $this->mytype = static::$xmlrpcTypes['array'];
168
            $this->me['array'] = $values;
169
170
            return 1;
171 1
        } elseif ($this->mytype == 2) {
172
            // we're adding to an array here
173 1
            $this->me['array'] = array_merge($this->me['array'], $values);
174
175 1
            return 1;
176
        } else {
177
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
178
            return 0;
179
        }
180
    }
181
182
    /**
183
     * Merges an array of named xmlrpc value objects into an xmlrpc value.
184
     *
185
     * If the xmlrpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
186
     * If the xmlrpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
187
     * Fails otherwise.
188
     *
189
     * @param Value[] $values
190
     *
191
     * @return int 1 or 0 on failure
192
     *
193
     * @todo add some checking for $values to be an array?
194
     */
195 1
    public function addStruct($values)
196
    {
197 1
        if ($this->mytype == 0) {
198
            $this->mytype = static::$xmlrpcTypes['struct'];
199
            $this->me['struct'] = $values;
200
201
            return 1;
202 1
        } elseif ($this->mytype == 3) {
203
            // we're adding to a struct here
204 1
            $this->me['struct'] = array_merge($this->me['struct'], $values);
205
206 1
            return 1;
207
        } else {
208
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
209
            return 0;
210
        }
211
    }
212
213
    /**
214
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
215
     *
216
     * @return string
217
     */
218 174
    public function kindOf()
219
    {
220 174
        switch ($this->mytype) {
221 174
            case 3:
222 77
                return 'struct';
223 174
            case 2:
224 154
                return 'array';
225 154
            case 1:
226 154
                return 'scalar';
227
            default:
228
                return 'undef';
229
        }
230
    }
231
232 530
    protected function serializedata($typ, $val, $charsetEncoding = '')
233
    {
234 530
        $rs = '';
235
236 530
        if (!isset(static::$xmlrpcTypes[$typ])) {
237
            return $rs;
238
        }
239
240 530
        switch (static::$xmlrpcTypes[$typ]) {
241 530
            case 1:
242
                switch ($typ) {
243 530
                    case static::$xmlrpcBase64:
244 20
                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
245 20
                        break;
246 511
                    case static::$xmlrpcBoolean:
247 21
                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
248 21
                        break;
249 492
                    case static::$xmlrpcString:
250
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
251 280
                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
252 280
                        break;
253 385
                    case static::$xmlrpcInt:
254 60
                    case static::$xmlrpcI4:
255 60
                    case static::$xmlrpcI8:
256 346
                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
257 346
                        break;
258 60
                    case static::$xmlrpcDouble:
259
                        // avoid using standard conversion of float to string because it is locale-dependent,
260
                        // and also because the xmlrpc spec forbids exponential notation.
261
                        // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
262
                        // The code below tries its best at keeping max precision while avoiding exp notation,
263
                        // but there is of course no limit in the number of decimal places to be used...
264 21
                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
265 21
                        break;
266 41
                    case static::$xmlrpcDateTime:
267 21
                        if (is_string($val)) {
268 21
                            $rs .= "<${typ}>${val}</${typ}>";
269 20
                        } elseif (is_a($val, 'DateTime')) {
270 20
                            $rs .= "<${typ}>" . $val->format('Ymd\TH:i:s') . "</${typ}>";
271 20
                        } elseif (is_int($val)) {
272 20
                            $rs .= "<${typ}>" . strftime("%Y%m%dT%H:%M:%S", $val) . "</${typ}>";
273
                        } else {
274
                            // not really a good idea here: but what shall we output anyway? left for backward compat...
275
                            $rs .= "<${typ}>${val}</${typ}>";
276
                        }
277 21
                        break;
278 21
                    case static::$xmlrpcNull:
279 21
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
280 21
                            $rs .= "<ex:nil/>";
281
                        } else {
282 1
                            $rs .= "<nil/>";
283
                        }
284 21
                        break;
285
                    default:
286
                        // no standard type value should arrive here, but provide a possibility
287
                        // for xmlrpc values of unknown type...
288
                        $rs .= "<${typ}>${val}</${typ}>";
289
                }
290 530
                break;
291 118
            case 3:
292
                // struct
293 79
                if ($this->_php_class) {
294
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
295
                } else {
296 79
                    $rs .= "<struct>\n";
297
                }
298 79
                $charsetEncoder = Charset::instance();
299
                /** @var Value $val2 */
300 79
                foreach ($val as $key2 => $val2) {
301 79
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
302
                    //$rs.=$this->serializeval($val2);
303 79
                    $rs .= $val2->serialize($charsetEncoding);
304 79
                    $rs .= "</member>\n";
305
                }
306 79
                $rs .= '</struct>';
307 79
                break;
308 98
            case 2:
309
                // array
310 98
                $rs .= "<array>\n<data>\n";
311
                /** @var Value $element */
312 98
                foreach ($val as $element) {
313
                    //$rs.=$this->serializeval($val[$i]);
314 79
                    $rs .= $element->serialize($charsetEncoding);
315
                }
316 98
                $rs .= "</data>\n</array>";
317 98
                break;
318
            default:
319
                break;
320
        }
321
322 530
        return $rs;
323
    }
324
325
    /**
326
     * Returns the xml representation of the value. XML prologue not included.
327
     *
328
     * @param string $charsetEncoding the charset to be used for serialization. if null, US-ASCII is assumed
329
     *
330
     * @return string
331
     */
332 530
    public function serialize($charsetEncoding = '')
333
    {
334 530
        $val = reset($this->me);
335 530
        $typ = key($this->me);
336
337 530
        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
338
    }
339
340
    /**
341
     * Checks whether a struct member with a given name is present.
342
     *
343
     * Works only on xmlrpc values of type struct.
344
     *
345
     * @param string $key the name of the struct member to be looked up
346
     *
347
     * @return boolean
348
     *
349
     * @deprecated use array access, e.g. isset($val[$key])
350
     */
351 2
    public function structmemexists($key)
352
    {
353
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
354
355 2
        return array_key_exists($key, $this->me['struct']);
356
    }
357
358
    /**
359
     * Returns the value of a given struct member (an xmlrpc value object in itself).
360
     * Will raise a php warning if struct member of given name does not exist.
361
     *
362
     * @param string $key the name of the struct member to be looked up
363
     *
364
     * @return Value
365
     *
366
     * @deprecated use array access, e.g. $val[$key]
367
     */
368 27
    public function structmem($key)
369
    {
370
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
371
372 27
        return $this->me['struct'][$key];
373
    }
374
375
    /**
376
     * Reset internal pointer for xmlrpc values of type struct.
377
     * @deprecated iterate directly over the object using foreach instead
378
     */
379
    public function structreset()
380
    {
381
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
382
383
        reset($this->me['struct']);
384
    }
385
386
    /**
387
     * Return next member element for xmlrpc values of type struct.
388
     *
389
     * @return Value
390
     * @throw \Error starting with php 8.0, this function should not be used, as it will always throw
391
     *
392
     * @deprecated iterate directly over the object using foreach instead
393
     */
394
    public function structeach()
395
    {
396
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
397
398
        return @each($this->me['struct']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return @each($this->me['struct']) returns the type array which is incompatible with the documented return type PhpXmlRpc\Value.
Loading history...
Deprecated Code introduced by
The function each() has been deprecated: 7.2 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

398
        return @/** @scrutinizer ignore-deprecated */ each($this->me['struct']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
399
    }
400
401
    /**
402
     * Returns the value of a scalar xmlrpc value (base 64 decoding is automatically handled here)
403
     *
404
     * @return mixed
405
     */
406 478
    public function scalarval()
407
    {
408 478
        $b = reset($this->me);
409
410 478
        return $b;
411
    }
412
413
    /**
414
     * Returns the type of the xmlrpc value.
415
     *
416
     * For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and returned as such
417
     *
418
     * @return string
419
     */
420 40
    public function scalartyp()
421
    {
422 40
        reset($this->me);
423 40
        $a = key($this->me);
424 40
        if ($a == static::$xmlrpcI4) {
425
            $a = static::$xmlrpcInt;
426
        }
427
428 40
        return $a;
429
    }
430
431
    /**
432
     * Returns the m-th member of an xmlrpc value of array type.
433
     *
434
     * @param integer $key the index of the value to be retrieved (zero based)
435
     *
436
     * @return Value
437
     *
438
     * @deprecated use array access, e.g. $val[$key]
439
     */
440 39
    public function arraymem($key)
441
    {
442
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
443
444 39
        return $this->me['array'][$key];
445
    }
446
447
    /**
448
     * Returns the number of members in an xmlrpc value of array type.
449
     *
450
     * @return integer
451
     *
452
     * @deprecated use count() instead
453
     */
454 40
    public function arraysize()
455
    {
456
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
457
458 40
        return count($this->me['array']);
459
    }
460
461
    /**
462
     * Returns the number of members in an xmlrpc value of struct type.
463
     *
464
     * @return integer
465
     *
466
     * @deprecated use count() instead
467
     */
468 21
    public function structsize()
469
    {
470
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
471
472 21
        return count($this->me['struct']);
473
    }
474
475
    /**
476
     * Returns the number of members in an xmlrpc value:
477
     * - 0 for uninitialized values
478
     * - 1 for scalar values
479
     * - the number of elements for struct and array values
480
     *
481
     * @return integer
482
     */
483 21
    public function count()
484
    {
485 21
        switch ($this->mytype) {
486 21
            case 3:
487
                return count($this->me['struct']);
488 21
            case 2:
489 21
                return count($this->me['array']);
490
            case 1:
491
                return 1;
492
            default:
493
                return 0;
494
        }
495
    }
496
497
    /**
498
     * Implements the IteratorAggregate interface
499
     *
500
     * @return \ArrayIterator
501
     */
502 135
    public function getIterator() {
503 135
        switch ($this->mytype) {
504 135
            case 3:
505 39
                return new \ArrayIterator($this->me['struct']);
506 116
            case 2:
507 116
                return new \ArrayIterator($this->me['array']);
508
            case 1:
509
                return new \ArrayIterator($this->me);
510
            default:
511
                return new \ArrayIterator();
512
        }
513
    }
514
515
    public function offsetSet($offset, $value) {
516
517
        switch ($this->mytype) {
518
            case 3:
519
                if (!($value instanceof \PhpXmlRpc\Value)) {
520
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
521
                }
522
                if (is_null($offset)) {
523
                    // disallow struct members with empty names
524
                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
525
                } else {
526
                    $this->me['struct'][$offset] = $value;
527
                }
528
                return;
529
            case 2:
530
                if (!($value instanceof \PhpXmlRpc\Value)) {
531
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
532
                }
533
                if (is_null($offset)) {
534
                    $this->me['array'][] = $value;
535
                } else {
536
                    // nb: we are not checking that $offset is above the existing array range...
537
                    $this->me['array'][$offset] = $value;
538
                }
539
                return;
540
            case 1:
541
// todo: handle i4 vs int
542
                reset($this->me);
543
                $type = key($this->me);
544
                if ($type != $offset) {
545
                    throw new \Exception('');
546
                }
547
                $this->me[$type] = $value;
548
                return;
549
            default:
550
                // it would be nice to allow empty values to be be turned into non-empty ones this way, but we miss info to do so
551
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
552
        }
553
    }
554
555
    public function offsetExists($offset) {
556
        switch ($this->mytype) {
557
            case 3:
558
                return isset($this->me['struct'][$offset]);
559
            case 2:
560
                return isset($this->me['array'][$offset]);
561
            case 1:
562
// todo: handle i4 vs int
563
                return $offset == $this->scalartyp();
564
            default:
565
                return false;
566
        }
567
    }
568
569
    public function offsetUnset($offset) {
570
        switch ($this->mytype) {
571
            case 3:
572
                unset($this->me['struct'][$offset]);
573
                return;
574
            case 2:
575
                unset($this->me['array'][$offset]);
576
                return;
577
            case 1:
578
                // can not remove value from a scalar
579
                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
580
            default:
581
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
582
        }
583
    }
584
585 99
    public function offsetGet($offset) {
586 99
        switch ($this->mytype) {
587 99
            case 3:
588 99
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
589 20
            case 2:
590 20
                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
591
            case 1:
592
// on bad type: null or exception?
593
                $value = reset($this->me);
594
                $type = key($this->me);
595
                return $type == $offset ? $value : null;
596
            default:
597
// return null or exception?
598
                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
599
        }
600
    }
601
}
602