Passed
Push — master ( b6cd05...9f5262 )
by Gaetano
05:39
created

Value::serializedata()   D

Complexity

Conditions 23
Paths 20

Size

Total Lines 92
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 23.1075

Importance

Changes 4
Bugs 2 Features 0
Metric Value
cc 23
eloc 63
c 4
b 2
f 0
nc 20
nop 3
dl 0
loc 92
ccs 48
cts 51
cp 0.9412
crap 23.1075
rs 4.1666

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\Traits\CharsetEncoderAware;
6
use PhpXmlRpc\Traits\LoggerAware;
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
    use CharsetEncoderAware;
14
    use LoggerAware;
15
16
    public static $xmlrpcI4 = "i4";
17
    public static $xmlrpcI8 = "i8";
18
    public static $xmlrpcInt = "int";
19
    public static $xmlrpcBoolean = "boolean";
20
    public static $xmlrpcDouble = "double";
21
    public static $xmlrpcString = "string";
22
    public static $xmlrpcDateTime = "dateTime.iso8601";
23
    public static $xmlrpcBase64 = "base64";
24
    public static $xmlrpcArray = "array";
25
    public static $xmlrpcStruct = "struct";
26
    public static $xmlrpcValue = "undefined";
27
    public static $xmlrpcNull = "null";
28
29
    public static $xmlrpcTypes = array(
30
        "i4" => 1,
31
        "i8" => 1,
32
        "int" => 1,
33
        "boolean" => 1,
34
        "double" => 1,
35
        "string" => 1,
36
        "dateTime.iso8601" => 1,
37
        "base64" => 1,
38
        "array" => 2,
39
        "struct" => 3,
40
        "null" => 1,
41
    );
42
43
    /// @todo: do these need to be public?
44
    /** @var Value[]|mixed */
45
    public $me = array();
46
    /**
47
     * @var int
48
     * @internal
49
     */
50
    public $mytype = 0;
51
    /** @var string|null */
52
    public $_php_class = null;
53
54 1
    /**
55
     * Build an xml-rpc value.
56 1
     *
57 1
     * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
58
     *
59 1
     * @param Value[]|mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
60
     * @param string $type any valid xml-rpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
61
     *                     base64, array, struct, null.
62
     *                     If null, 'string' is assumed.
63
     *                     You should refer to http://xmlrpc.com/spec.md for more information on what each of these mean.
64
     */
65
    public function __construct($val = -1, $type = '')
66
    {
67 594
        // optimization creep - do not call addXX, do it all inline.
68
        // downside: booleans will not be coerced anymore
69 594
        if ($val !== -1 || $type != '') {
70 435
            switch ($type) {
71
                case '':
72 594
                    $this->mytype = 1;
73
                    $this->me['string'] = $val;
74
                    break;
75
                case 'i4':
76
                case 'i8':
77
                case 'int':
78
                case 'double':
79
                case 'string':
80
                case 'boolean':
81
                case 'dateTime.iso8601':
82
                case 'base64':
83
                case 'null':
84
                    $this->mytype = 1;
85
                    $this->me[$type] = $val;
86
                    break;
87
                case 'array':
88
                    $this->mytype = 2;
89
                    $this->me['array'] = $val;
90
                    break;
91 746
                case 'struct':
92
                    $this->mytype = 3;
93
                    $this->me['struct'] = $val;
94
                    break;
95 746
                default:
96 745
                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
97 745
            }
98 251
        }
99 251
    }
100 251
101 743
    /**
102 743
     * Add a single php value to an xml-rpc value.
103 743
     *
104 698
     * If the xml-rpc value is an array, the php value is added as its last element.
105 677
     * If the xml-rpc value is empty (uninitialized), this method makes it a scalar value, and sets that value.
106 469
     * Fails if the xml-rpc value is not an array (i.e. a struct or a scalar) and already initialized.
107 448
     *
108 425
     * @param mixed $val
109 404
     * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null.
110 717
     * @return int 1 or 0 on failure
111 717
     */
112 717
    public function addScalar($val, $type = 'string')
113 403
    {
114 241
        $typeOf = null;
115 241
        if (isset(static::$xmlrpcTypes[$type])) {
116 241
            $typeOf = static::$xmlrpcTypes[$type];
117 295
        }
118 295
119 295
        if ($typeOf !== 1) {
120 295
            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
121
            return 0;
122
        }
123
124
        // coerce booleans into correct values
125 746
        /// @todo we should either do it for datetimes, integers, i8 and doubles, too,
126
        ///       or just plain remove this check, implemented on booleans only...
127
        if ($type == static::$xmlrpcBoolean) {
128
            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
129
                $val = true;
130
            } else {
131
                $val = false;
132
            }
133
        }
134
135
        switch ($this->mytype) {
136
            case 1:
137
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
138
                return 0;
139 1
            case 3:
140
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
141 1
                return 0;
142 1
            case 2:
143 1
                // we're adding a scalar value to an array here
144
                $class = get_class($this);
145
                $this->me['array'][] = new $class($val, $type);
146 1
147
                return 1;
148
            default:
149
                // a scalar, so set the value and remember we're scalar
150
                $this->me[$type] = $val;
151
                $this->mytype = $typeOf;
152
153
                return 1;
154 1
        }
155
    }
156
157
    /**
158
     * Add an array of xml-rpc value objects to an xml-rpc value.
159
     *
160
     * If the xml-rpc value is an array, the elements are appended to the existing ones.
161
     * If the xml-rpc value is empty (uninitialized), this method makes it an array value, and sets that value.
162 1
     * Fails otherwise.
163 1
     *
164
     * @param Value[] $values
165
     * @return int 1 or 0 on failure
166 1
     *
167 1
     * @todo add some checking for $values to be an array of xml-rpc values?
168 1
     */
169
    public function addArray($values)
170
    {
171
        if ($this->mytype == 0) {
172
            $this->mytype = static::$xmlrpcTypes['array'];
173
            $this->me['array'] = $values;
174
175
            return 1;
176
        } elseif ($this->mytype == 2) {
177
            // we're adding to an array here
178
            $this->me['array'] = array_merge($this->me['array'], $values);
179
180
            return 1;
181
        } else {
182
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
183
            return 0;
184
        }
185
    }
186
187
    /**
188
     * Merges an array of named xml-rpc value objects into an xml-rpc value.
189
     *
190
     * If the xml-rpc value is a struct, the elements are merged with the existing ones (overwriting existing ones).
191
     * If the xml-rpc value is empty (uninitialized), this method makes it a struct value, and sets that value.
192
     * Fails otherwise.
193
     *
194
     * @param Value[] $values
195
     * @return int 1 or 0 on failure
196 1
     *
197
     * @todo add some checking for $values to be an array?
198 1
     */
199
    public function addStruct($values)
200
    {
201
        if ($this->mytype == 0) {
202
            $this->mytype = static::$xmlrpcTypes['struct'];
203 1
            $this->me['struct'] = $values;
204
205 1
            return 1;
206
        } elseif ($this->mytype == 3) {
207 1
            // we're adding to a struct here
208
            $this->me['struct'] = array_merge($this->me['struct'], $values);
209
210
            return 1;
211
        } else {
212
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
213
            return 0;
214
        }
215
    }
216
217
    /**
218
     * Returns a string containing either "struct", "array", "scalar" or "undef", describing the base type of the value.
219
     *
220
     * @return string
221
     */
222
    public function kindOf()
223
    {
224
        switch ($this->mytype) {
225
            case 3:
226
                return 'struct';
227 1
            case 2:
228
                return 'array';
229 1
            case 1:
230
                return 'scalar';
231
            default:
232
                return 'undef';
233
        }
234 1
    }
235
236 1
    /**
237
     * @param string $typ
238 1
     * @param Value[]|mixed $val
239
     * @param string $charsetEncoding
240
     * @return string
241
     */
242
    protected function serializedata($typ, $val, $charsetEncoding = '')
243
    {
244
        $rs = '';
245
246
        if (!isset(static::$xmlrpcTypes[$typ])) {
247
            return $rs;
248
        }
249
250 627
        switch (static::$xmlrpcTypes[$typ]) {
251
            case 1:
252 627
                switch ($typ) {
253 627
                    case static::$xmlrpcBase64:
254 150
                        $rs .= "<{$typ}>" . base64_encode($val) . "</{$typ}>";
0 ignored issues
show
Bug introduced by
It seems like $val can also be of type PhpXmlRpc\Value[]; however, parameter $string of base64_encode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

254
                        $rs .= "<{$typ}>" . base64_encode(/** @scrutinizer ignore-type */ $val) . "</{$typ}>";
Loading history...
255 606
                        break;
256 214
                    case static::$xmlrpcBoolean:
257 583
                        $rs .= "<{$typ}>" . ($val ? '1' : '0') . "</{$typ}>";
258 583
                        break;
259
                    case static::$xmlrpcString:
260
                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
261
                        $rs .= "<{$typ}>" . $this->getCharsetEncoder()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</{$typ}>";
262
                        break;
263
                    case static::$xmlrpcInt:
264
                    case static::$xmlrpcI4:
265
                    case static::$xmlrpcI8:
266
                        $rs .= "<{$typ}>" . (int)$val . "</{$typ}>";
267
                        break;
268
                    case static::$xmlrpcDouble:
269
                        // avoid using standard conversion of float to string because it is locale-dependent,
270 705
                        // and also because the xml-rpc spec forbids exponential notation.
271
                        // sprintf('%F') could be most likely ok, but it fails e.g. on 2e-14.
272 705
                        // The code below tries its best at keeping max precision while avoiding exp notation,
273
                        // but there is of course no limit in the number of decimal places to be used...
274 705
                        $rs .= "<{$typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, PhpXmlRpc::$xmlpc_double_precision, '.', '')) . "</{$typ}>";
275 2
                        break;
276
                    case static::$xmlrpcDateTime:
277
                        if (is_string($val)) {
278 705
                            $rs .= "<{$typ}>{$val}</{$typ}>";
279 705
                        // DateTimeInterface is not present in php 5.4...
280
                        } elseif (is_a($val, 'DateTimeInterface') || is_a($val, 'DateTime')) {
281 684
                            $rs .= "<{$typ}>" . $val->format('Ymd\TH:i:s') . "</{$typ}>";
282 23
                        } elseif (is_int($val)) {
283 23
                            $rs .= "<{$typ}>" . date('Ymd\TH:i:s', $val) . "</{$typ}>";
284 684
                        } else {
285 46
                            // not really a good idea here: but what should we output anyway? left for backward compat...
286 46
                            $rs .= "<{$typ}>{$val}</{$typ}>";
287 663
                        }
288
                        break;
289 573
                    case static::$xmlrpcNull:
290 573
                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
291 474
                            $rs .= "<ex:nil/>";
292 68
                        } else {
293 68
                            $rs .= "<nil/>";
294 430
                        }
295 430
                        break;
296 68
                    default:
297
                        // no standard type value should arrive here, but provide a possibility
298
                        // for xml-rpc values of unknown type...
299
                        $rs .= "<{$typ}>{$val}</{$typ}>";
300
                }
301
                break;
302 24
            case 3:
303 24
                // struct
304 47
                if ($this->_php_class) {
305 25
                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
306 24
                } else {
307 23
                    $rs .= "<struct>\n";
308 22
                }
309 23
                $charsetEncoder = $this->getCharsetEncoder();
310 23
                /** @var Value $val2 */
311
                foreach ($val as $key2 => $val2) {
312
                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
313
                    //$rs.=$this->serializeval($val2);
314
                    $rs .= $val2->serialize($charsetEncoding);
315 25
                    $rs .= "</member>\n";
316 23
                }
317 23
                $rs .= '</struct>';
318 23
                break;
319
            case 2:
320 1
                // array
321
                $rs .= "<array>\n<data>\n";
322 23
                /** @var Value $element */
323
                foreach ($val as $element) {
324
                    //$rs.=$this->serializeval($val[$i]);
325
                    $rs .= $element->serialize($charsetEncoding);
326
                }
327
                $rs .= "</data>\n</array>";
328 684
                break;
329 218
            default:
330
                break;
331 133
        }
332 1
333
        return $rs;
334 133
    }
335
336 133
    /**
337
     * Returns the xml representation of the value. XML prologue not included.
338 133
     *
339 112
     * @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed
340
     * @return string
341 112
     */
342 112
    public function serialize($charsetEncoding = '')
343
    {
344 133
        $val = reset($this->me);
345 133
        $typ = key($this->me);
346 154
347
        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
348 154
    }
349
350 154
    /**
351
     * Checks whether a struct member with a given name is present.
352 154
     *
353
     * Works only on xml-rpc values of type struct.
354 154
     *
355 154
     * @param string $key the name of the struct member to be looked up
356
     * @return boolean
357
     *
358
     * @deprecated use array access, e.g. isset($val[$key])
359
     */
360 705
    public function structmemexists($key)
361
    {
362
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
363
364
        return array_key_exists($key, $this->me['struct']);
365
    }
366
367
    /**
368
     * Returns the value of a given struct member (an xml-rpc value object in itself).
369
     * Will raise a php warning if struct member of given name does not exist.
370 705
     *
371
     * @param string $key the name of the struct member to be looked up
372 705
     * @return Value
373 705
     *
374
     * @deprecated use array access, e.g. $val[$key]
375 705
     */
376
    public function structmem($key)
377
    {
378
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
379
380
        return $this->me['struct'][$key];
381
    }
382
383
    /**
384
     * Reset internal pointer for xml-rpc values of type struct.
385
     * @return void
386
     *
387
     * @deprecated iterate directly over the object using foreach instead
388
     */
389 2
    public function structreset()
390
    {
391
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
392
393 2
        reset($this->me['struct']);
394
    }
395
396
    /**
397
     * Return next member element for xml-rpc values of type struct.
398
     *
399
     * @return Value
400
     * @throws \Error starting with php 8.0, this function should not be used, as it will always throw
401
     *
402
     * @deprecated iterate directly over the object using foreach instead
403
     */
404
    public function structeach()
405
    {
406 29
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
407
408
        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...
409
    }
410 29
411
    /**
412
     * Returns the value of a scalar xml-rpc value (base 64 decoding is automatically handled here)
413
     *
414
     * @return mixed
415
     */
416
    public function scalarval()
417
    {
418
        $b = reset($this->me);
419
420
        return $b;
421
    }
422
423
    /**
424
     * Returns the type of the xml-rpc value.
425
     *
426
     * For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and returned as such
427
     *
428
     * @return string
429
     */
430
    public function scalartyp()
431
    {
432
        reset($this->me);
433
        $a = key($this->me);
434
        if ($a == static::$xmlrpcI4) {
435
            $a = static::$xmlrpcInt;
436
        }
437
438
        return $a;
439
    }
440
441
    /**
442
     * Returns the m-th member of an xml-rpc value of array type.
443
     *
444 660
     * @param integer $key the index of the value to be retrieved (zero based)
445
     *
446 660
     * @return Value
447
     *
448 660
     * @deprecated use array access, e.g. $val[$key]
449
     */
450
    public function arraymem($key)
451
    {
452
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
453
454
        return $this->me['array'][$key];
455
    }
456
457
    /**
458 454
     * Returns the number of members in an xml-rpc value of array type.
459
     *
460 454
     * @return integer
461 454
     *
462 454
     * @deprecated use count() instead
463
     */
464
    public function arraysize()
465
    {
466 454
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
467
468
        return count($this->me['array']);
469
    }
470
471
    /**
472
     * Returns the number of members in an xml-rpc value of struct type.
473
     *
474
     * @return integer
475
     *
476
     * @deprecated use count() instead
477
     */
478 43
    public function structsize()
479
    {
480
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
481
482 43
        return count($this->me['struct']);
483
    }
484
485
    /**
486
     * Returns the number of members in an xml-rpc value:
487
     * - 0 for uninitialized values
488
     * - 1 for scalar values
489
     * - the number of elements for struct and array values
490
     *
491
     * @return integer
492 44
     */
493
    #[\ReturnTypeWillChange]
494
    public function count()
495
    {
496 44
        switch ($this->mytype) {
497
            case 3:
498
                return count($this->me['struct']);
499
            case 2:
500
                return count($this->me['array']);
501
            case 1:
502
                return 1;
503
            default:
504
                return 0;
505
        }
506 23
    }
507
508
    /**
509
     * Implements the IteratorAggregate interface
510 23
     * @internal required to be public to implement an Interface
511
     *
512
     * @return \ArrayIterator
513
     */
514
    #[\ReturnTypeWillChange]
515
    public function getIterator()
516
    {
517
        switch ($this->mytype) {
518
            case 3:
519
                return new \ArrayIterator($this->me['struct']);
520
            case 2:
521
                return new \ArrayIterator($this->me['array']);
522 25
            case 1:
523
                return new \ArrayIterator($this->me);
524 25
            default:
525 25
                return new \ArrayIterator();
526
        }
527 25
    }
528 25
529
    /**
530
     * @internal required to be public to implement an Interface
531
     *
532
     * @param mixed $offset
533
     * @param mixed $value
534
     * @return void
535
     *
536
     * @throws \Exception
537
     */
538
    #[\ReturnTypeWillChange]
539
    public function offsetSet($offset, $value)
540
    {
541
        switch ($this->mytype) {
542
            case 3:
543 258
                if (!($value instanceof \PhpXmlRpc\Value)) {
544
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
545 258
                }
546 258
                if (is_null($offset)) {
547 65
                    // disallow struct members with empty names
548 215
                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
549 215
                } else {
550
                    $this->me['struct'][$offset] = $value;
551
                }
552
                return;
553
            case 2:
554
                if (!($value instanceof \PhpXmlRpc\Value)) {
555
                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
556
                }
557
                if (is_null($offset)) {
558
                    $this->me['array'][] = $value;
559
                } else {
560
                    // nb: we are not checking that $offset is above the existing array range...
561
                    $this->me['array'][$offset] = $value;
562
                }
563
                return;
564 22
            case 1:
565
// todo: handle i4 vs int
566 22
                reset($this->me);
567 22
                $type = key($this->me);
568
                if ($type != $offset) {
569
                    throw new \Exception('');
570
                }
571
                $this->me[$type] = $value;
572
                return;
573
            default:
574
                // it would be nice to allow empty values to be turned into non-empty ones this way, but we miss info to do so
575
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
576
        }
577
    }
578 22
579 22
    /**
580
     * @internal required to be public to implement an Interface
581
     *
582 22
     * @param mixed $offset
583 22
     * @return bool
584
     */
585
    #[\ReturnTypeWillChange]
586
    public function offsetExists($offset)
587
    {
588 22
        switch ($this->mytype) {
589
            case 3:
590
                return isset($this->me['struct'][$offset]);
591
            case 2:
592
                return isset($this->me['array'][$offset]);
593
            case 1:
594
// todo: handle i4 vs int
595
                return $offset == $this->scalartyp();
596
            default:
597
                return false;
598
        }
599
    }
600
601
    /**
602
     * @internal required to be public to implement an Interface
603
     *
604
     * @param mixed $offset
605
     * @return void
606
     *
607
     * @throws \Exception
608
     */
609
    #[\ReturnTypeWillChange]
610
    public function offsetUnset($offset)
611
    {
612
        switch ($this->mytype) {
613
            case 3:
614
                unset($this->me['struct'][$offset]);
615
                return;
616
            case 2:
617
                unset($this->me['array'][$offset]);
618
                return;
619
            case 1:
620
                // can not remove value from a scalar
621
                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
622
            default:
623
                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
624
        }
625
    }
626
627
    /**
628
     * @internal required to be public to implement an Interface
629
     *
630
     * @param mixed $offset
631
     * @return mixed|Value|null
632
     * @throws \Exception
633
     */
634
    #[\ReturnTypeWillChange]
635
    public function offsetGet($offset)
636
    {
637
        switch ($this->mytype) {
638
            case 3:
639
                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
640
            case 2:
641
                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
642
            case 1:
643
// on bad type: null or exception?
644
                $value = reset($this->me);
645
                $type = key($this->me);
646
                return $type == $offset ? $value : null;
647
            default:
648
// return null or exception?
649
                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
650
        }
651
    }
652
}
653