AMQPAbstractCollection::setValue()   B
last analyzed

Complexity

Conditions 11
Paths 20

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 11.0086

Importance

Changes 0
Metric Value
cc 11
eloc 29
nc 20
nop 3
dl 0
loc 41
ccs 23
cts 24
cp 0.9583
crap 11.0086
rs 7.3166
c 0
b 0
f 0

How to fix   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 PhpAmqpLib\Wire;
4
5
use PhpAmqpLib\Channel\AbstractChannel;
6
use PhpAmqpLib\Exception;
7
use PhpAmqpLib\Wire;
8
9
/**
10
 * Iterator implemented for transparent integration with AMQPWriter::write_[array|table]()
11
 */
12
abstract class AMQPAbstractCollection implements \Iterator, \ArrayAccess
13
{
14
    //protocol defines available field types and their corresponding symbols
15
    const PROTOCOL_RBT = 'rabbit'; //pseudo proto
16
17
    //Abstract data types
18
    const T_INT_SHORTSHORT = 1;
19
    const T_INT_SHORTSHORT_U = 2;
20
    const T_INT_SHORT = 3;
21
    const T_INT_SHORT_U = 4;
22
    const T_INT_LONG = 5;
23
    const T_INT_LONG_U = 6;
24
    const T_INT_LONGLONG = 7;
25
    const T_INT_LONGLONG_U = 8;
26
27
    const T_DECIMAL = 9;
28
    const T_TIMESTAMP = 10;
29
    const T_VOID = 11;
30
31
    const T_BOOL = 12;
32
33
    const T_STRING_SHORT = 13;
34
    const T_STRING_LONG = 14;
35
36
    const T_ARRAY = 15;
37
    const T_TABLE = 16;
38
39
    const T_BYTES = 17;
40
41
    const T_FLOAT = 18;
42
    const T_DOUBLE = 19;
43
44
    /**
45
     * @var string
46
     */
47
    private static $protocol;
48
49
    /*
50
     * Field types messy mess http://www.rabbitmq.com/amqp-0-9-1-errata.html#section_3
51
     * Default behaviour is to use rabbitMQ compatible field-set
52
     * Define AMQP_STRICT_FLD_TYPES=true to use strict AMQP instead
53
     * @var array<int, string>
54
     */
55
    private static $types_080 = array(
56
        self::T_INT_LONG => 'I',
57
        self::T_DECIMAL => 'D',
58
        self::T_TIMESTAMP => 'T',
59
        self::T_STRING_LONG => 'S',
60
        self::T_TABLE => 'F'
61
    );
62
63
    /**
64
     * @var array<int, string>
65
     */
66
    private static $types_091 = array(
67
        self::T_INT_SHORTSHORT => 'b',
68
        self::T_INT_SHORTSHORT_U => 'B',
69
        self::T_INT_SHORT => 'U',
70
        self::T_INT_SHORT_U => 'u',
71
        self::T_INT_LONG => 'I',
72
        self::T_INT_LONG_U => 'i',
73
        self::T_INT_LONGLONG => 'L',
74
        self::T_INT_LONGLONG_U => 'l',
75
        self::T_FLOAT => 'f',
76
        self::T_DOUBLE => 'd',
77
        self::T_DECIMAL => 'D',
78
        self::T_TIMESTAMP => 'T',
79
        self::T_VOID => 'V',
80
        self::T_BOOL => 't',
81
        self::T_STRING_SHORT => 's',
82
        self::T_STRING_LONG => 'S',
83
        self::T_ARRAY => 'A',
84
        self::T_TABLE => 'F',
85
        self::T_BYTES => 'x',
86
    );
87
88
    /**
89
     * @var array<int, string>
90
     */
91
    private static $types_rabbit = array(
92
        self::T_INT_SHORTSHORT => 'b',
93
        self::T_INT_SHORTSHORT_U => 'B',
94
        self::T_INT_SHORT => 's',
95
        self::T_INT_SHORT_U => 'u',
96
        self::T_INT_LONG => 'I',
97
        self::T_INT_LONG_U => 'i',
98
        self::T_INT_LONGLONG => 'l',
99
        self::T_FLOAT => 'f',
100
        self::T_DOUBLE => 'd',
101
        self::T_DECIMAL => 'D',
102
        self::T_TIMESTAMP => 'T',
103
        self::T_VOID => 'V',
104
        self::T_BOOL => 't',
105
        self::T_STRING_LONG => 'S',
106
        self::T_ARRAY => 'A',
107
        self::T_TABLE => 'F',
108
        self::T_BYTES => 'x',
109
    );
110
111
    /**
112
     * @var array
113
     */
114
    protected $data = array();
115
116 39
    public function __construct(array $data = null)
117
    {
118 39
        if (!empty($data)) {
119 22
            $this->data = $this->encodeCollection($data);
120
        }
121
    }
122
123
    /**
124
     * @return int
125
     */
126
    abstract public function getType();
127
128
    /**
129
     * @param mixed $val
130
     * @param int|null $type
131
     * @param string $key
132
     */
133 21
    final protected function setValue($val, $type = null, $key = null)
134
    {
135 21
        if ($val instanceof self) {
136 7
            if ($type && ($type !== $val->getType())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
137 1
                throw new Exception\AMQPInvalidArgumentException(
138 1
                    sprintf(
139
                        'Attempted to add instance of %s representing type [%s] as mismatching type [%s]',
140 1
                        get_class($val),
141 1
                        $val->getType(),
142
                        $type
143
                    )
144
                );
145
            }
146 6
            $type = $val->getType();
147 19
        } elseif ($type) { //ensuring data integrity and that all members are properly validated
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
148
            switch ($type) {
149 18
                case self::T_ARRAY:
150 1
                    throw new Exception\AMQPInvalidArgumentException('Arrays must be passed as AMQPArray instance');
151 17
                case self::T_TABLE:
152 1
                    throw new Exception\AMQPInvalidArgumentException('Tables must be passed as AMQPTable instance');
153 16
                case self::T_DECIMAL:
154 1
                    if (!($val instanceof AMQPDecimal)) {
155 1
                        throw new Exception\AMQPInvalidArgumentException(
156
                            'Decimal values must be instance of AMQPDecimal'
157
                        );
158
                    }
159
                    break;
160
            }
161
        }
162
163 17
        if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
164 16
            self::checkDataTypeIsSupported($type, false);
165 13
            $val = array($type, $val);
166
        } else {
167 1
            $val = $this->encodeValue($val);
168
        }
169
170 14
        if ($key === null) {
171 5
            $this->data[] = $val;
172
        } else {
173 12
            $this->data[$key] = $val;
174
        }
175
    }
176
177
    /**
178
     * @return array
179
     */
180 16
    final public function getNativeData()
181
    {
182 16
        return $this->decodeCollection($this->data);
183
    }
184
185
    /**
186
     * @param array $val
187
     * @return array
188
     */
189 22
    final protected function encodeCollection(array $val)
190
    {
191 22
        foreach ($val as $k => $v) {
192 22
            $val[$k] = $this->encodeValue($v);
193
        }
194
195 21
        return $val;
196
    }
197
198
    /**
199
     * @param array $val
200
     * @return array
201
     */
202 16
    final protected function decodeCollection(array $val)
203
    {
204 16
        foreach ($val as $k => $v) {
205 16
            $val[$k] = $this->decodeValue($v[1], $v[0]);
206
        }
207
208 16
        return $val;
209
    }
210
211 1
    public function offsetExists($offset): bool
212
    {
213 1
        return isset($this->data[$offset]);
214
    }
215
216
    /**
217
     * @param mixed $offset
218
     * @return mixed
219
     */
220
    #[\ReturnTypeWillChange]
221 1
    public function offsetGet($offset)
222
    {
223 1
        $value = isset($this->data[$offset]) ? $this->data[$offset] : null;
224
225 1
        return is_array($value) ? $value[1] : $value;
226
    }
227
228 1
    public function offsetSet($offset, $value): void
229
    {
230 1
        $this->setValue($value, null, $offset);
231
    }
232
233 1
    public function offsetUnset($offset): void
234
    {
235 1
        unset($this->data[$offset]);
236
    }
237
238
    /**
239
     * @param mixed $val
240
     * @return mixed
241
     * @throws Exception\AMQPOutOfBoundsException
242
     */
243 23
    protected function encodeValue($val)
244
    {
245 23
        if (is_string($val)) {
246 21
            $val = $this->encodeString($val);
247 18
        } elseif (is_float($val)) {
248 10
            $val = $this->encodeFloat($val);
249 18
        } elseif (is_int($val)) {
250 15
            $val = $this->encodeInt($val);
251 18
        } elseif (is_bool($val)) {
252 15
            $val = $this->encodeBool($val);
253 16
        } elseif (is_null($val)) {
254 7
            $val = $this->encodeVoid();
255 15
        } elseif ($val instanceof \DateTimeInterface) {
256 1
            $val = array(self::T_TIMESTAMP, $val->getTimestamp());
257 15
        } elseif ($val instanceof AMQPDecimal) {
258
            $val = array(self::T_DECIMAL, $val);
259 15
        } elseif ($val instanceof self) {
260
            //avoid silent type correction of strictly typed values
261
            self::checkDataTypeIsSupported($val->getType(), false);
262
            $val = array($val->getType(), $val);
263 15
        } elseif (is_array($val)) {
264
            //AMQP specs says "Field names MUST start with a letter, '$' or '#'"
265
            //so beware, some servers may raise an exception with 503 code in cases when indexed
266
            // array is encoded as table
267 14
            if (self::isProtocol(Wire\Constants080::VERSION)) {
268
                //080 doesn't support arrays, forcing table
269 3
                $val = array(self::T_TABLE, new AMQPTable($val));
270 11
            } elseif (empty($val) || (array_keys($val) === range(0, count($val) - 1))) {
271 10
                $val = array(self::T_ARRAY, new AMQPArray($val));
272
            } else {
273 14
                $val = array(self::T_TABLE, new AMQPTable($val));
274
            }
275
        } else {
276 1
            throw new Exception\AMQPOutOfBoundsException(
277 1
                sprintf('Encountered value of unsupported type: %s', gettype($val))
278
            );
279
        }
280
281 22
        return $val;
282
    }
283
284
    /**
285
     * @param mixed $val
286
     * @param int $type
287
     * @return array|bool|\DateTime|null
288
     */
289 16
    protected function decodeValue($val, $type)
290
    {
291 16
        if ($val instanceof self) {
292
            //covering arrays and tables
293 10
            $val = $val->getNativeData();
294
        } else {
295
            switch ($type) {
296 15
                case self::T_BOOL:
297 6
                    $val = (bool) $val;
298 6
                    break;
299 15
                case self::T_TIMESTAMP:
300
                    $val = \DateTime::createFromFormat('U', $val);
301
                    break;
302 15
                case self::T_VOID:
303 5
                    $val = null;
304 5
                    break;
305 14
                case self::T_ARRAY:
306 14
                case self::T_TABLE:
307
                    throw new Exception\AMQPLogicException(
308
                        sprintf(
309
                            '%s %s',
310
                            'Encountered an array/table struct which is not an instance of AMQPCollection.',
311
                            'This is considered a bug and should be fixed, please report'
312
                        )
313
                    );
314
            }
315
        }
316
317 16
        return $val;
318
    }
319
320
    /**
321
     * @param string $val
322
     * @return array
323
     */
324 21
    protected function encodeString($val)
325
    {
326 21
        return array(self::T_STRING_LONG, $val);
327
    }
328
329
    /**
330
     * @param int $val
331
     * @return array
332
     */
333 15
    protected function encodeInt($val)
334
    {
335 15
        if (($val >= -2147483648) && ($val <= 2147483647)) {
336 15
            $ev = array(self::T_INT_LONG, $val);
337 11
        } elseif (self::isProtocol(Wire\Constants080::VERSION)) {
338
            //080 doesn't support longlong
339 3
            $ev = $this->encodeString((string) $val);
340
        } else {
341 8
            $ev = array(self::T_INT_LONGLONG, $val);
342
        }
343
344 15
        return $ev;
345
    }
346
347
    /**
348
     * @param float $val
349
     * @return array
350
     */
351 10
    protected function encodeFloat($val)
352
    {
353 10
        return $this->encodeString((string) $val);
354
    }
355
356
    /**
357
     * @param bool $val
358
     * @return array
359
     */
360 15
    protected function encodeBool($val)
361
    {
362 15
        $val = (bool) $val;
363
364 15
        return self::isProtocol(Wire\Constants080::VERSION)
365 3
            ? array(self::T_INT_LONG, (int) $val)
366 15
            : array(self::T_BOOL, $val);
367
    }
368
369
    /**
370
     * @return array
371
     */
372 7
    protected function encodeVoid()
373
    {
374 7
        return self::isProtocol(Wire\Constants080::VERSION) ? $this->encodeString('') : array(self::T_VOID, null);
375
    }
376
377
    /**
378
     * @return string
379
     */
380 81
    final public static function getProtocol()
381
    {
382 81
        if (self::$protocol === null) {
0 ignored issues
show
introduced by
The condition self::protocol === null is always false.
Loading history...
383 1
            self::$protocol = defined('AMQP_STRICT_FLD_TYPES') && AMQP_STRICT_FLD_TYPES ?
0 ignored issues
show
Bug introduced by
The constant PhpAmqpLib\Wire\AMQP_STRICT_FLD_TYPES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
384
                AbstractChannel::getProtocolVersion() :
385 1
                self::PROTOCOL_RBT;
386
        }
387
388 81
        return self::$protocol;
389
    }
390
391
    /**
392
     * @param string $proto
393
     * @return bool
394
     */
395 17
    final public static function isProtocol($proto)
396
    {
397 17
        return self::getProtocol() === $proto;
398
    }
399
400
    /**
401
     * @return array  [dataTypeConstant => dataTypeSymbol]
402
     */
403 75
    final public static function getSupportedDataTypes()
404
    {
405 75
        switch ($proto = self::getProtocol()) {
406
            case Wire\Constants080::VERSION:
407 2
                $types = self::$types_080;
408 2
                break;
409 73
            case Wire\Constants091::VERSION:
410 10
                $types = self::$types_091;
411 10
                break;
412 64
            case self::PROTOCOL_RBT:
413 64
                $types = self::$types_rabbit;
414 64
                break;
415
            default:
416
                throw new Exception\AMQPOutOfRangeException(sprintf('Unknown protocol: %s', $proto));
417
        }
418
419 75
        return $types;
420
    }
421
422
    /**
423
     * @param string $type
424
     * @param bool $return Whether to return or raise AMQPOutOfRangeException
425
     * @return boolean
426
     */
427 16
    final public static function checkDataTypeIsSupported($type, $return = true)
428
    {
429
        try {
430 16
            $supported = self::getSupportedDataTypes();
431 16
            if (!isset($supported[$type])) {
432 3
                throw new Exception\AMQPOutOfRangeException(sprintf(
433
                    'AMQP-%s doesn\'t support data of type [%s]',
434 3
                    self::getProtocol(),
435
                    $type
436
                ));
437
            }
438 13
            return true;
439 3
        } catch (Exception\AMQPOutOfRangeException $ex) {
440 3
            if (!$return) {
441 3
                throw $ex;
442
            }
443
444
            return false;
445
        }
446
    }
447
448
    /**
449
     * @param int $type
450
     * @return string
451
     */
452 64
    final public static function getSymbolForDataType($type)
453
    {
454 64
        $types = self::getSupportedDataTypes();
455 64
        if (!isset($types[$type])) {
456
            throw new Exception\AMQPOutOfRangeException(sprintf(
457
                'AMQP-%s doesn\'t support data of type [%s]',
458
                self::getProtocol(),
459
                $type
460
            ));
461
        }
462
463 64
        return $types[$type];
464
    }
465
466
    /**
467
     * @param string $symbol
468
     * @return integer
469
     */
470 69
    final public static function getDataTypeForSymbol($symbol)
471
    {
472 69
        $symbols = array_flip(self::getSupportedDataTypes());
473 69
        if (!isset($symbols[$symbol])) {
474 1
            throw new Exception\AMQPOutOfRangeException(sprintf(
475
                'AMQP-%s doesn\'t define data of type [%s]',
476 1
                self::getProtocol(),
477
                $symbol
478
            ));
479
        }
480
481 68
        return $symbols[$symbol];
482
    }
483
484
    /**
485
     * @return mixed
486
     */
487
    #[\ReturnTypeWillChange]
488 11
    public function current()
489
    {
490 11
        return current($this->data);
491
    }
492
493
    /**
494
     * @return mixed
495
     */
496
    #[\ReturnTypeWillChange]
497 5
    public function key()
498
    {
499 5
        return key($this->data);
500
    }
501
502 11
    public function next(): void
503
    {
504 11
        next($this->data);
505
    }
506
507 12
    public function rewind(): void
508
    {
509 12
        reset($this->data);
510
    }
511
512 12
    public function valid(): bool
513
    {
514 12
        return key($this->data) !== null;
515
    }
516
}
517