AMQPReader   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 427
Duplicated Lines 0 %

Test Coverage

Coverage 86.62%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 165
dl 0
loc 427
ccs 123
cts 142
cp 0.8662
rs 3.28
c 3
b 0
f 0
wmc 64

24 Methods

Rating   Name   Duplication   Size   Complexity  
A tell() 0 3 1
A read_float() 0 6 1
A read_php_int() 0 9 2
A read_signed_octet() 0 6 1
A read_signed_short() 0 6 1
A read_bit() 0 12 2
A resetCounters() 0 3 1
A read_longlong() 0 22 5
A read_octet() 0 6 1
A read_long() 0 9 3
A read() 0 5 1
A read_table_object() 0 3 1
D read_value() 0 70 20
A read_double() 0 6 1
A readFrameHeader() 0 3 1
A read_shortstr() 0 6 1
A read_table() 0 20 5
A read_array_object() 0 3 1
A read_signed_longlong() 0 24 6
A read_longstr() 0 10 2
A readSignedLong() 0 6 1
A read_timestamp() 0 3 1
A read_array() 0 18 4
A read_short() 0 6 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace PhpAmqpLib\Wire;
4
5
use PhpAmqpLib\Channel\Frame;
6
use PhpAmqpLib\Exception\AMQPInvalidArgumentException;
7
use PhpAmqpLib\Exception\AMQPOutOfBoundsException;
8
use PhpAmqpLib\Helper\BigInteger;
9
10
abstract class AMQPReader extends AMQPByteStream
11
{
12
    /** @var int */
13
    protected $offset = 0;
14
15
    /** @var int */
16
    protected $bitcount = 0;
17
18
    /** @var int */
19
    protected $bits = 0;
20
21
    /**
22
     * Close the byte stream.
23
     */
24
    abstract public function close(): void;
25
26
    abstract protected function rawread(int $n): string;
27
28
    /**
29
     * @param int $n
30
     * @return string
31
     */
32
    public function read($n)
33
    {
34
        $this->resetCounters();
35
36
        return $this->rawread($n);
37
    }
38
39
    public function read_bit(): bool
40
    {
41
        if (empty($this->bitcount)) {
42
            $this->bits = ord($this->rawread(1));
43
            $this->bitcount = 8;
44
        }
45
46
        $result = ($this->bits & 1) === 1;
47
        $this->bits >>= 1;
48
        $this->bitcount--;
49
50
        return $result;
51
    }
52
53
    /**
54
     * @return int
55
     */
56
    public function read_octet()
57 687
    {
58
        $this->resetCounters();
59 687
        list(, $res) = unpack('C', $this->rawread(1));
60 676
61 676
        return $res;
62
    }
63 687
64 687
    /**
65
     * @return int
66
     */
67
    public function read_signed_octet()
68
    {
69
        $this->resetCounters();
70
        list(, $res) = unpack('c', $this->rawread(1));
71
72
        return $res;
73
    }
74
75
    /**
76
     * @return int
77 62
     */
78
    public function read_short()
79 62
    {
80 62
        $this->resetCounters();
81 62
        list(, $res) = unpack('n', $this->rawread(2));
82 62
83
        return $res;
84
    }
85
86
    /**
87
     * @return int
88 40
     */
89
    public function read_signed_short()
90 40
    {
91 40
        $this->resetCounters();
92
        list(, $res) = unpack('s', $this->correctEndianness($this->rawread(2)));
93
94
        return $res;
95
    }
96
97
    /**
98
     * Reads 32 bit integer in big-endian byte order.
99 47
     *
100
     * On 64 bit systems it will return always unsigned int
101 47
     * value in 0..2^32 range.
102
     *
103 47
     * On 32 bit systems it will return signed int value in
104
     * -2^31...+2^31 range.
105
     *
106
     * Use with caution!
107
     * @return int|string
108
     */
109
    public function read_php_int()
110
    {
111
        list(, $res) = unpack('N', $this->rawread(4));
112
113
        if (self::PLATFORM_64BIT) {
114 47
            return (int) sprintf('%u', $res);
115
        }
116 47
117 47
        return $res;
118
    }
119 4
120 4
    /**
121 47
     * PHP does not have unsigned 32 bit int,
122 47
     * so we return it as a string
123
     *
124
     * @return int|string
125 36
     */
126 36
    public function read_long()
127
    {
128
        $this->resetCounters();
129 47
        list(, $res) = unpack('N', $this->rawread(4));
130
        if (!self::PLATFORM_64BIT && $this->getLongMSB($res)) {
131 47
            return sprintf('%u', $res);
132 13
        }
133 5
134
        return $res;
135
    }
136
137
    /**
138 8
     * @return int
139
     */
140
    private function readSignedLong()
141
    {
142
        $this->resetCounters();
143
        list(, $res) = unpack('l', $this->correctEndianness($this->rawread(4)));
144
145
        return $res;
146
    }
147
148
    /**
149
     * Even on 64 bit systems PHP integers are signed.
150 684
     * Since we need an unsigned value here we return it as a string.
151
     *
152 684
     * @return int|string
153 47
     */
154 47
    public function read_longlong()
155 47
    {
156
        $this->resetCounters();
157 47
        $bytes = $this->rawread(8);
158 47
159
        if (self::PLATFORM_64BIT) {
160
            // we can "unpack" if MSB bit is 0 (at most 63 bit integer), fallback to BigInteger otherwise
161
            if (!$this->getMSB($bytes)) {
162
                $res = unpack('J', $bytes);
163
                return $res[1];
164
            }
165 47
        } else {
166 47
            // on 32-bit systems we can "unpack" up to 31 bits integer
167
            list(, $hi, $lo) = unpack('N2', $bytes);
168
            if ($hi === 0 && $lo > 0) {
169 684
                return $lo;
170
            }
171
        }
172
173
        $var = new BigInteger($bytes, 256);
174
175
        return $var->toString();
176
    }
177 684
178 684
    /**
179 684
     * @return int|string
180 684
     */
181
    public function read_signed_longlong()
182 684
    {
183
        $this->resetCounters();
184
        $bytes = $this->rawread(8);
185
186
        if (self::PLATFORM_64BIT) {
187
            $res = unpack('q', $this->correctEndianness($bytes));
188 16
            return $res[1];
189
        } else {
190 16
            // on 32-bit systems we can "unpack" up to 31 bits integer
191 16
            list(, $hi, $lo) = unpack('N2', $bytes);
192 16
            if ($hi === 0 && $lo > 0) {
193
                // positive and less than 2^31-1
194
                return $lo;
195 16
            }
196 16
            // negative and more than -2^31
197 16
            if ($hi === -1 && $this->getLongMSB($lo)) {
198
                return $lo;
199 16
            }
200
        }
201
202
        $var = new BigInteger($bytes, -256);
203
204
        return $var->toString();
205 311
    }
206
207 311
    /**
208 311
     * @return float
209
     */
210 311
    public function read_float()
211
    {
212
        $this->resetCounters();
213
        list(, $res) = unpack('G', $this->rawread(4));
214
215
        return (float)$res;
216 256
    }
217
218 256
    /**
219 256
     * @return float
220
     */
221 256
    public function read_double()
222
    {
223
        $this->resetCounters();
224
        list(, $res) = unpack('E', $this->rawread(8));
225
226
        return (float)$res;
227 62
    }
228
229 62
    /**
230 62
     * Read a utf-8 encoded string that's stored in up to
231
     * 255 bytes.  Return it decoded as a PHP unicode object.
232 62
     * @return string
233
     */
234
    public function read_shortstr()
235
    {
236
        $this->resetCounters();
237
        list(, $slen) = unpack('C', $this->rawread(1));
238 4
239
        return $this->rawread($slen);
240 4
    }
241 4
242
    /**
243 4
     * Read a string that's up to 2**32 bytes, the encoding
244
     * isn't specified in the AMQP spec, so just return it as
245
     * a plain PHP string.
246
     * @return string
247
     */
248
    public function read_longstr()
249
    {
250
        $this->resetCounters();
251
        $slen = $this->read_php_int();
252
253
        if ($slen < 0) {
254
            throw new AMQPOutOfBoundsException('Strings longer than supported on this platform');
255
        }
256
257
        return $this->rawread($slen);
0 ignored issues
show
Bug introduced by
It seems like $slen can also be of type string; however, parameter $n of PhpAmqpLib\Wire\AMQPReader::rawread() does only seem to accept integer, 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

257
        return $this->rawread(/** @scrutinizer ignore-type */ $slen);
Loading history...
258 62
    }
259
260 62
    /**
261
     * Read and AMQP timestamp, which is a 64-bit integer representing
262 62
     * seconds since the Unix epoch in 1-second resolution.
263 62
     * @return int|string
264
     */
265
    public function read_timestamp()
266
    {
267
        return $this->read_longlong();
268
    }
269
270
    /**
271
     * Read an AMQP table, and return as a PHP array. keys are strings,
272
     * values are (type,value) tuples.
273
     *
274
     * @param bool $returnObject Whether to return AMQPArray instance instead of plain array
275 64
     * @return array|AMQPTable
276
     */
277 64
    public function read_table(bool $returnObject = false)
278 64
    {
279 64
        $this->resetCounters();
280
        $tlen = $this->read_php_int();
281
282
        if ($tlen < 0) {
283 64
            throw new AMQPOutOfBoundsException('Table is longer than supported');
284
        }
285
286
        $table_data = new AMQPBufferReader($this->rawread($tlen));
0 ignored issues
show
Bug introduced by
It seems like $tlen can also be of type string; however, parameter $n of PhpAmqpLib\Wire\AMQPReader::rawread() does only seem to accept integer, 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

286
        $table_data = new AMQPBufferReader($this->rawread(/** @scrutinizer ignore-type */ $tlen));
Loading history...
287
        $result = $returnObject ? new AMQPTable() : array();
288
289 27
        while ($table_data->tell() < $tlen) {
290
            $name = $table_data->read_shortstr();
291 27
            $ftype = AMQPAbstractCollection::getDataTypeForSymbol($ftypeSym = $table_data->rawread(1));
292 27
            $val = $table_data->read_value($ftype, $returnObject);
293
            $returnObject ? $result->set($name, $val, $ftype) : $result[$name] = array($ftypeSym, $val);
294 27
        }
295
296
        return $result;
297
    }
298
299
    /**
300
     * @return array|AMQPTable
301
     */
302
    public function read_table_object()
303 36
    {
304
        return $this->read_table(true);
305 36
    }
306 36
307
    /**
308 36
     * Reads the array in the next value.
309
     *
310 36
     * @param bool $returnObject Whether to return AMQPArray instance instead of plain array
311 31
     * @return array|AMQPArray
312 36
     */
313
    public function read_array($returnObject = false)
314
    {
315
        $this->resetCounters();
316
317
        // Determine array length and its end position
318
        $arrayLength = $this->read_php_int();
319
        $endOffset = $this->offset + $arrayLength;
320
321
        $result = $returnObject ? new AMQPArray() : array();
322 5
323
        // Read values until we reach the end of the array
324 5
        while ($this->offset < $endOffset) {
325
            $fieldType = AMQPAbstractCollection::getDataTypeForSymbol($this->rawread(1));
326
            $fieldValue = $this->read_value($fieldType, $returnObject);
327
            $returnObject ? $result->push($fieldValue, $fieldType) : $result[] = $fieldValue;
328
        }
329
330 37
        return $result;
331
    }
332 37
333 37
    /**
334
     * @return array|AMQPArray
335 37
     */
336 37
    public function read_array_object()
337 37
    {
338
        return $this->read_array(true);
339
    }
340
341
    /**
342
     * @return array{type:int, channel:int, size:int}
343
     */
344
    public function readFrameHeader(): array
345
    {
346
        return unpack('Ctype/nchannel/Nsize', $this->rawread(Frame::FRAME_HEADER_SIZE));
347
    }
348
349
    /**
350
     * Reads the next value as the provided field type.
351
     *
352
     * @param int $fieldType One of AMQPAbstractCollection::T_* constants
353
     * @param bool $collectionsAsObjects Description
354
     * @return mixed
355
     * @throws \PhpAmqpLib\Exception\AMQPDataReadException
356
     */
357
    public function read_value(int $fieldType, bool $collectionsAsObjects = false)
358
    {
359 1
        $this->resetCounters();
360
361 1
        switch ($fieldType) {
362 1
            case AMQPAbstractCollection::T_INT_SHORTSHORT:
363
                //according to AMQP091 spec, 'b' is not bit, it is short-short-int, also valid for rabbit/qpid
364 1
                //$val=$this->read_bit();
365
                $val = $this->read_signed_octet();
366
                break;
367
            case AMQPAbstractCollection::T_INT_SHORTSHORT_U:
368
            case AMQPAbstractCollection::T_BOOL:
369
                $val = $this->read_octet();
370 1
                break;
371
            case AMQPAbstractCollection::T_INT_SHORT:
372 1
                $val = $this->read_signed_short();
373 1
                break;
374
            case AMQPAbstractCollection::T_INT_SHORT_U:
375 1
                $val = $this->read_short();
376
                break;
377
            case AMQPAbstractCollection::T_INT_LONG:
378
                $val = $this->readSignedLong();
379
                break;
380
            case AMQPAbstractCollection::T_INT_LONG_U:
381
                $val = $this->read_long();
382
                break;
383 63
            case AMQPAbstractCollection::T_INT_LONGLONG:
384
                $val = $this->read_signed_longlong();
385 63
                break;
386 63
            case AMQPAbstractCollection::T_INT_LONGLONG_U:
387
                $val = $this->read_longlong();
388 63
                break;
389
            case AMQPAbstractCollection::T_DECIMAL:
390
                $e = $this->read_octet();
391
                $n = $this->readSignedLong();
392
                $val = new AMQPDecimal($n, $e);
393
                break;
394
            case AMQPAbstractCollection::T_TIMESTAMP:
395
                $val = $this->read_timestamp();
396
                break;
397 57
            case AMQPAbstractCollection::T_STRING_SHORT:
398
                $val = $this->read_shortstr();
399 57
                break;
400 57
            case AMQPAbstractCollection::T_STRING_LONG:
401
            case AMQPAbstractCollection::T_BYTES:
402 57
                $val = $this->read_longstr();
403
                break;
404
            case AMQPAbstractCollection::T_ARRAY:
405
                $val = $this->read_array($collectionsAsObjects);
406 57
                break;
407
            case AMQPAbstractCollection::T_TABLE:
408
                $val = $this->read_table($collectionsAsObjects);
409
                break;
410
            case AMQPAbstractCollection::T_VOID:
411
                $val = null;
412
                break;
413
            case AMQPAbstractCollection::T_FLOAT:
414 1
                $val = $this->read_float();
415
                break;
416 1
            case AMQPAbstractCollection::T_DOUBLE:
417
                $val = $this->read_double();
418
                break;
419
            default:
420
                throw new AMQPInvalidArgumentException(sprintf(
421
                    'Unsupported type "%s"',
422
                    $fieldType
423
                ));
424
        }
425
426 58
        return $val;
427
    }
428 58
429 58
    protected function tell(): int
430
    {
431 58
        return $this->offset;
432
    }
433
434
    protected function resetCounters(): void
435 58
    {
436 58
        $this->bitcount = $this->bits = 0;
437
    }
438
}
439