AMQPAbstractCollection   F
last analyzed

Complexity

Total Complexity 76

Size/Duplication

Total Lines 505
Duplicated Lines 0 %

Test Coverage

Coverage 91.88%

Importance

Changes 4
Bugs 1 Features 1
Metric Value
eloc 226
dl 0
loc 505
ccs 147
cts 160
cp 0.9188
rs 2.32
c 4
b 1
f 1
wmc 76

27 Methods

Rating   Name   Duplication   Size   Complexity  
A encodeFloat() 0 3 1
A isProtocol() 0 3 1
A offsetGet() 0 6 3
C encodeValue() 0 39 13
A current() 0 4 1
A rewind() 0 3 1
B setValue() 0 41 11
A getNativeData() 0 3 1
A encodeVoid() 0 3 2
A next() 0 3 1
A offsetExists() 0 3 1
A encodeBool() 0 7 2
A getSymbolForDataType() 0 12 2
A valid() 0 3 1
A offsetSet() 0 3 1
B decodeValue() 0 29 7
A encodeCollection() 0 7 2
A offsetUnset() 0 3 1
A encodeString() 0 3 1
A __construct() 0 4 2
A checkDataTypeIsSupported() 0 18 4
A decodeCollection() 0 7 2
A getDataTypeForSymbol() 0 12 2
A encodeInt() 0 12 4
A key() 0 4 1
A getProtocol() 0 9 4
A getSupportedDataTypes() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like AMQPAbstractCollection 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 AMQPAbstractCollection, and based on these observations, apply Extract Interface, too.

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
     * @deprecated
55
     */
56
    private static $types_080 = array(
57
        self::T_INT_LONG => 'I',
58
        self::T_DECIMAL => 'D',
59
        self::T_TIMESTAMP => 'T',
60
        self::T_STRING_LONG => 'S',
61
        self::T_TABLE => 'F'
62
    );
63
64
    /**
65
     * @var array<int, string>
66
     */
67
    private static $types_091 = array(
68
        self::T_INT_SHORTSHORT => 'b',
69
        self::T_INT_SHORTSHORT_U => 'B',
70
        self::T_INT_SHORT => 'U',
71
        self::T_INT_SHORT_U => 'u',
72
        self::T_INT_LONG => 'I',
73
        self::T_INT_LONG_U => 'i',
74
        self::T_INT_LONGLONG => 'L',
75
        self::T_INT_LONGLONG_U => 'l',
76
        self::T_FLOAT => 'f',
77
        self::T_DOUBLE => 'd',
78
        self::T_DECIMAL => 'D',
79
        self::T_TIMESTAMP => 'T',
80
        self::T_VOID => 'V',
81
        self::T_BOOL => 't',
82
        self::T_STRING_SHORT => 's',
83
        self::T_STRING_LONG => 'S',
84
        self::T_ARRAY => 'A',
85
        self::T_TABLE => 'F',
86
        self::T_BYTES => 'x',
87
    );
88
89
    /**
90
     * @var array<int, string>
91
     */
92
    private static $types_rabbit = array(
93
        self::T_INT_SHORTSHORT => 'b',
94
        self::T_INT_SHORTSHORT_U => 'B',
95
        self::T_INT_SHORT => 's',
96
        self::T_INT_SHORT_U => 'u',
97
        self::T_INT_LONG => 'I',
98
        self::T_INT_LONG_U => 'i',
99
        self::T_INT_LONGLONG => 'l',
100
        self::T_FLOAT => 'f',
101
        self::T_DOUBLE => 'd',
102
        self::T_DECIMAL => 'D',
103
        self::T_TIMESTAMP => 'T',
104
        self::T_VOID => 'V',
105
        self::T_BOOL => 't',
106
        self::T_STRING_LONG => 'S',
107
        self::T_ARRAY => 'A',
108
        self::T_TABLE => 'F',
109
        self::T_BYTES => 'x',
110
    );
111
112
    /**
113
     * @var array
114
     */
115
    protected $data = array();
116 39
117
    public function __construct(?array $data = null)
118 39
    {
119 22
        if (!empty($data)) {
120
            $this->data = $this->encodeCollection($data);
121
        }
122
    }
123
124
    /**
125
     * @return int
126
     */
127
    abstract public function getType();
128
129
    /**
130
     * @param mixed $val
131
     * @param int|null $type
132
     * @param string $key
133 21
     */
134
    final protected function setValue($val, $type = null, $key = null)
135 21
    {
136 7
        if ($val instanceof self) {
137 1
            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...
138 1
                throw new Exception\AMQPInvalidArgumentException(
139
                    sprintf(
140 1
                        'Attempted to add instance of %s representing type [%s] as mismatching type [%s]',
141 1
                        get_class($val),
142
                        $val->getType(),
143
                        $type
144
                    )
145
                );
146 6
            }
147 19
            $type = $val->getType();
148
        } 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...
149 18
            switch ($type) {
150 1
                case self::T_ARRAY:
151 17
                    throw new Exception\AMQPInvalidArgumentException('Arrays must be passed as AMQPArray instance');
152 1
                case self::T_TABLE:
153 16
                    throw new Exception\AMQPInvalidArgumentException('Tables must be passed as AMQPTable instance');
154 1
                case self::T_DECIMAL:
155 1
                    if (!($val instanceof AMQPDecimal)) {
156
                        throw new Exception\AMQPInvalidArgumentException(
157
                            'Decimal values must be instance of AMQPDecimal'
158
                        );
159
                    }
160
                    break;
161
            }
162
        }
163 17
164 16
        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...
165 13
            self::checkDataTypeIsSupported($type, false);
166
            $val = array($type, $val);
167 1
        } else {
168
            $val = $this->encodeValue($val);
169
        }
170 14
171 5
        if ($key === null) {
172
            $this->data[] = $val;
173 12
        } else {
174
            $this->data[$key] = $val;
175
        }
176
    }
177
178
    /**
179
     * @return array
180 16
     */
181
    final public function getNativeData()
182 16
    {
183
        return $this->decodeCollection($this->data);
184
    }
185
186
    /**
187
     * @param array $val
188
     * @return array
189 22
     */
190
    final protected function encodeCollection(array $val)
191 22
    {
192 22
        foreach ($val as $k => $v) {
193
            $val[$k] = $this->encodeValue($v);
194
        }
195 21
196
        return $val;
197
    }
198
199
    /**
200
     * @param array $val
201
     * @return array
202 16
     */
203
    final protected function decodeCollection(array $val)
204 16
    {
205 16
        foreach ($val as $k => $v) {
206
            $val[$k] = $this->decodeValue($v[1], $v[0]);
207
        }
208 16
209
        return $val;
210
    }
211 1
212
    public function offsetExists($offset): bool
213 1
    {
214
        return isset($this->data[$offset]);
215
    }
216
217
    /**
218
     * @param mixed $offset
219
     * @return mixed
220
     */
221 1
    #[\ReturnTypeWillChange]
222
    public function offsetGet($offset)
223 1
    {
224
        $value = isset($this->data[$offset]) ? $this->data[$offset] : null;
225 1
226
        return is_array($value) ? $value[1] : $value;
227
    }
228 1
229
    public function offsetSet($offset, $value): void
230 1
    {
231
        $this->setValue($value, null, $offset);
232
    }
233 1
234
    public function offsetUnset($offset): void
235 1
    {
236
        unset($this->data[$offset]);
237
    }
238
239
    /**
240
     * @param mixed $val
241
     * @return mixed
242
     * @throws Exception\AMQPOutOfBoundsException
243 23
     */
244
    protected function encodeValue($val)
245 23
    {
246 21
        if (is_string($val)) {
247 18
            $val = $this->encodeString($val);
248 10
        } elseif (is_float($val)) {
249 18
            $val = $this->encodeFloat($val);
250 15
        } elseif (is_int($val)) {
251 18
            $val = $this->encodeInt($val);
252 15
        } elseif (is_bool($val)) {
253 16
            $val = $this->encodeBool($val);
254 7
        } elseif (is_null($val)) {
255 15
            $val = $this->encodeVoid();
256 1
        } elseif ($val instanceof \DateTimeInterface) {
257 15
            $val = array(self::T_TIMESTAMP, $val->getTimestamp());
258
        } elseif ($val instanceof AMQPDecimal) {
259 15
            $val = array(self::T_DECIMAL, $val);
260
        } elseif ($val instanceof self) {
261
            //avoid silent type correction of strictly typed values
262
            self::checkDataTypeIsSupported($val->getType(), false);
263 15
            $val = array($val->getType(), $val);
264
        } elseif (is_array($val)) {
265
            //AMQP specs says "Field names MUST start with a letter, '$' or '#'"
266
            //so beware, some servers may raise an exception with 503 code in cases when indexed
267 14
            // array is encoded as table
268
            if (self::isProtocol(Wire\Constants080::VERSION)) {
269 3
                //080 doesn't support arrays, forcing table
270 11
                $val = array(self::T_TABLE, new AMQPTable($val));
271 10
            } elseif (empty($val) || (array_keys($val) === range(0, count($val) - 1))) {
272
                $val = array(self::T_ARRAY, new AMQPArray($val));
273 14
            } else {
274
                $val = array(self::T_TABLE, new AMQPTable($val));
275
            }
276 1
        } else {
277 1
            throw new Exception\AMQPOutOfBoundsException(
278
                sprintf('Encountered value of unsupported type: %s', gettype($val))
279
            );
280
        }
281 22
282
        return $val;
283
    }
284
285
    /**
286
     * @param mixed $val
287
     * @param int $type
288
     * @return array|bool|\DateTime|null
289 16
     */
290
    protected function decodeValue($val, $type)
291 16
    {
292
        if ($val instanceof self) {
293 10
            //covering arrays and tables
294
            $val = $val->getNativeData();
295
        } else {
296 15
            switch ($type) {
297 6
                case self::T_BOOL:
298 6
                    $val = (bool) $val;
299 15
                    break;
300
                case self::T_TIMESTAMP:
301
                    $val = \DateTime::createFromFormat('U', $val);
302 15
                    break;
303 5
                case self::T_VOID:
304 5
                    $val = null;
305 14
                    break;
306 14
                case self::T_ARRAY:
307
                case self::T_TABLE:
308
                    throw new Exception\AMQPLogicException(
309
                        sprintf(
310
                            '%s %s',
311
                            'Encountered an array/table struct which is not an instance of AMQPCollection.',
312
                            'This is considered a bug and should be fixed, please report'
313
                        )
314
                    );
315
            }
316
        }
317 16
318
        return $val;
319
    }
320
321
    /**
322
     * @param string $val
323
     * @return array
324 21
     */
325
    protected function encodeString($val)
326 21
    {
327
        return array(self::T_STRING_LONG, $val);
328
    }
329
330
    /**
331
     * @param int $val
332
     * @return array
333 15
     */
334
    protected function encodeInt($val)
335 15
    {
336 15
        if (($val >= -2147483648) && ($val <= 2147483647)) {
337 11
            $ev = array(self::T_INT_LONG, $val);
338
        } elseif (self::isProtocol(Wire\Constants080::VERSION)) {
339 3
            //080 doesn't support longlong
340
            $ev = $this->encodeString((string) $val);
341 8
        } else {
342
            $ev = array(self::T_INT_LONGLONG, $val);
343
        }
344 15
345
        return $ev;
346
    }
347
348
    /**
349
     * @param float $val
350
     * @return array
351 10
     */
352
    protected function encodeFloat($val)
353 10
    {
354
        return $this->encodeString((string) $val);
355
    }
356
357
    /**
358
     * @param bool $val
359
     * @return array
360 15
     */
361
    protected function encodeBool($val)
362 15
    {
363
        $val = (bool) $val;
364 15
365 3
        return self::isProtocol(Wire\Constants080::VERSION)
366 15
            ? array(self::T_INT_LONG, (int) $val)
367
            : array(self::T_BOOL, $val);
368
    }
369
370
    /**
371
     * @return array
372 7
     */
373
    protected function encodeVoid()
374 7
    {
375
        return self::isProtocol(Wire\Constants080::VERSION) ? $this->encodeString('') : array(self::T_VOID, null);
376
    }
377
378
    /**
379
     * @return string
380 81
     * @deprecated
381
     */
382 81
    final public static function getProtocol()
383 1
    {
384
        if (self::$protocol === null) {
0 ignored issues
show
introduced by
The condition self::protocol === null is always false.
Loading history...
385 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...
386
                AbstractChannel::getProtocolVersion() :
387
                self::PROTOCOL_RBT;
388 81
        }
389
390
        return self::$protocol;
391
    }
392
393
    /**
394
     * @param string $proto
395 17
     * @return bool
396
     */
397 17
    final public static function isProtocol($proto)
398
    {
399
        return self::getProtocol() === $proto;
0 ignored issues
show
Deprecated Code introduced by
The function PhpAmqpLib\Wire\AMQPAbst...llection::getProtocol() has been deprecated. ( Ignorable by Annotation )

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

399
        return /** @scrutinizer ignore-deprecated */ self::getProtocol() === $proto;
Loading history...
400
    }
401
402
    /**
403 75
     * @return array  [dataTypeConstant => dataTypeSymbol]
404
     */
405 75
    final public static function getSupportedDataTypes()
406
    {
407 2
        switch ($proto = self::getProtocol()) {
0 ignored issues
show
Deprecated Code introduced by
The function PhpAmqpLib\Wire\AMQPAbst...llection::getProtocol() has been deprecated. ( Ignorable by Annotation )

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

407
        switch ($proto = /** @scrutinizer ignore-deprecated */ self::getProtocol()) {
Loading history...
408 2
            case Wire\Constants080::VERSION:
409 73
                $types = self::$types_080;
410 10
                break;
411 10
            case Wire\Constants091::VERSION:
412 64
                $types = self::$types_091;
413 64
                break;
414 64
            case self::PROTOCOL_RBT:
415
                $types = self::$types_rabbit;
416
                break;
417
            default:
418
                throw new Exception\AMQPOutOfRangeException(sprintf('Unknown protocol: %s', $proto));
419 75
        }
420
421
        return $types;
422
    }
423
424
    /**
425
     * @param string $type
426
     * @param bool $return Whether to return or raise AMQPOutOfRangeException
427 16
     * @return boolean
428
     */
429
    final public static function checkDataTypeIsSupported($type, $return = true)
430 16
    {
431 16
        try {
432 3
            $supported = self::getSupportedDataTypes();
433
            if (!isset($supported[$type])) {
434 3
                throw new Exception\AMQPOutOfRangeException(sprintf(
435
                    'AMQP-%s doesn\'t support data of type [%s]',
436
                    self::getProtocol(),
0 ignored issues
show
Deprecated Code introduced by
The function PhpAmqpLib\Wire\AMQPAbst...llection::getProtocol() has been deprecated. ( Ignorable by Annotation )

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

436
                    /** @scrutinizer ignore-deprecated */ self::getProtocol(),
Loading history...
437
                    $type
438 13
                ));
439 3
            }
440 3
            return true;
441 3
        } catch (Exception\AMQPOutOfRangeException $ex) {
442
            if (!$return) {
443
                throw $ex;
444
            }
445
446
            return false;
447
        }
448
    }
449
450
    /**
451
     * @param int $type
452 64
     * @return string
453
     */
454 64
    final public static function getSymbolForDataType($type)
455 64
    {
456
        $types = self::getSupportedDataTypes();
457
        if (!isset($types[$type])) {
458
            throw new Exception\AMQPOutOfRangeException(sprintf(
459
                'AMQP-%s doesn\'t support data of type [%s]',
460
                self::getProtocol(),
0 ignored issues
show
Deprecated Code introduced by
The function PhpAmqpLib\Wire\AMQPAbst...llection::getProtocol() has been deprecated. ( Ignorable by Annotation )

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

460
                /** @scrutinizer ignore-deprecated */ self::getProtocol(),
Loading history...
461
                $type
462
            ));
463 64
        }
464
465
        return $types[$type];
466
    }
467
468
    /**
469
     * @param string $symbol
470 69
     * @return integer
471
     */
472 69
    final public static function getDataTypeForSymbol($symbol)
473 69
    {
474 1
        $symbols = array_flip(self::getSupportedDataTypes());
475
        if (!isset($symbols[$symbol])) {
476 1
            throw new Exception\AMQPOutOfRangeException(sprintf(
477
                'AMQP-%s doesn\'t define data of type [%s]',
478
                self::getProtocol(),
0 ignored issues
show
Deprecated Code introduced by
The function PhpAmqpLib\Wire\AMQPAbst...llection::getProtocol() has been deprecated. ( Ignorable by Annotation )

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

478
                /** @scrutinizer ignore-deprecated */ self::getProtocol(),
Loading history...
479
                $symbol
480
            ));
481 68
        }
482
483
        return $symbols[$symbol];
484
    }
485
486
    /**
487
     * @return mixed
488 11
     */
489
    #[\ReturnTypeWillChange]
490 11
    public function current()
491
    {
492
        return current($this->data);
493
    }
494
495
    /**
496
     * @return mixed
497 5
     */
498
    #[\ReturnTypeWillChange]
499 5
    public function key()
500
    {
501
        return key($this->data);
502 11
    }
503
504 11
    public function next(): void
505
    {
506
        next($this->data);
507 12
    }
508
509 12
    public function rewind(): void
510
    {
511
        reset($this->data);
512 12
    }
513
514 12
    public function valid(): bool
515
    {
516
        return key($this->data) !== null;
517
    }
518
}
519