Completed
Pull Request — master (#12)
by Eugene
05:55
created

Packer   C

Complexity

Total Complexity 64

Size/Duplication

Total Lines 282
Duplicated Lines 7.8 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 64
lcom 1
cbo 4
dl 22
loc 282
ccs 134
cts 134
cp 1
rs 5.8364
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setTypeDetectionMode() 0 9 4
A setTransformers() 0 4 1
A getTransformers() 0 4 1
A __construct() 0 6 2
C pack() 0 50 16
A packArray() 0 11 2
A packArrayHeader() 11 11 3
A packMap() 0 12 2
A packMapHeader() 11 11 3
A packStr() 0 16 4
A packBin() 0 13 3
C packExt() 0 23 8
A packNil() 0 4 1
A packBool() 0 4 2
A packFloat() 0 4 1
D packInt() 0 34 10
A packUint64() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Packer 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

1
<?php
2
3
/*
4
 * This file is part of the rybakit/msgpack.php package.
5
 *
6
 * (c) Eugene Leonovich <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace MessagePack;
13
14
use MessagePack\Exception\PackingFailedException;
15
use MessagePack\TypeTransformer\Collection;
16
17
class Packer
18
{
19
    const FORCE_STR = 0b0001;
20
    const FORCE_BIN = 0b0010;
21
    const FORCE_ARR = 0b0100;
22
    const FORCE_MAP = 0b1000;
23
24
    private $strDetectionMode;
25
    private $arrDetectionMode;
26
27
    const NON_UTF8_REGEX = '/(
28
        [\xC0-\xC1] # Invalid UTF-8 Bytes
29
        | [\xF5-\xFF] # Invalid UTF-8 Bytes
30
        | \xE0[\x80-\x9F] # Overlong encoding of prior code point
31
        | \xF0[\x80-\x8F] # Overlong encoding of prior code point
32
        | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
33
        | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
34
        | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
35
        | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
36
        | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
37
        | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
38
        | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
39
        | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
40
    )/x';
41
42
    /**
43
     * @var Collection
44
     */
45
    private $transformers;
46
47
    /**
48
     * @param int|null $typeDetectionMode
49
     */
50 112
    public function __construct($typeDetectionMode = null)
51
    {
52 112
        if (null !== $typeDetectionMode) {
53 12
            $this->setTypeDetectionMode($typeDetectionMode);
54 12
        }
55 112
    }
56
57
    /**
58
     * @param int $mode
59
     */
60 34
    public function setTypeDetectionMode($mode)
61
    {
62 34
        if ($mode > 0b1010 || $mode < 0 || 0b11 === ($mode & 0b11)) {
63 10
            throw new \InvalidArgumentException('Invalid type detection mode.');
64
        }
65
66 24
        $this->strDetectionMode = $mode & 0b0011;
67 24
        $this->arrDetectionMode = $mode & 0b1100;
68 24
    }
69
70
    /**
71
     * @param Collection|null $transformers
72
     */
73 2
    public function setTransformers(Collection $transformers = null)
74
    {
75 2
        $this->transformers = $transformers;
76 2
    }
77
78
    /**
79
     * @return Collection|null
80
     */
81 1
    public function getTransformers()
82
    {
83 1
        return $this->transformers;
84
    }
85
86 101
    public function pack($value)
87
    {
88 101
        if (\is_int($value)) {
89 55
            return $this->packInt($value);
90
        }
91 72
        if (\is_string($value)) {
92 39
            if (!$this->strDetectionMode) {
93 27
                return \preg_match(self::NON_UTF8_REGEX, $value)
94 27
                    ? $this->packBin($value)
95 27
                    : $this->packStr($value);
96
            }
97 12
            if (self::FORCE_STR === $this->strDetectionMode) {
98 6
                return $this->packStr($value);
99
            }
100
101 6
            return $this->packBin($value);
102
        }
103 47
        if (\is_array($value)) {
104 30
            if (!$this->arrDetectionMode) {
105 18
                return \array_values($value) === $value
106 18
                    ? $this->packArray($value)
107 18
                    : $this->packMap($value);
108
            }
109 12
            if (self::FORCE_ARR === $this->arrDetectionMode) {
110 6
                return $this->packArray($value);
111
            }
112
113 6
            return $this->packMap($value);
114
        }
115 21
        if (null === $value) {
116 3
            return $this->packNil();
117
        }
118 20
        if (\is_bool($value)) {
119 6
            return $this->packBool($value);
120
        }
121 15
        if (\is_double($value)) {
122 3
            return $this->packFloat($value);
123
        }
124 12
        if ($value instanceof Ext) {
125 9
            return $this->packExt($value);
126
        }
127
128 3
        if ($this->transformers && $transformer = $this->transformers->match($value)) {
129 1
            $ext = new Ext($transformer->getId(), $this->pack($transformer->transform($value)));
130
131 1
            return $this->packExt($ext);
132
        }
133
134 2
        throw new PackingFailedException($value, 'Unsupported type.');
135
    }
136
137 15
    public function packArray(array $array)
138
    {
139 15
        $size = \count($array);
140 15
        $data = self::packArrayHeader($size);
141
142 15
        foreach ($array as $val) {
143 14
            $data .= $this->pack($val);
144 15
        }
145
146 15
        return $data;
147
    }
148
149 15 View Code Duplication
    private static function packArrayHeader($size)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
    {
151 15
        if ($size <= 0xf) {
152 12
            return \chr(0x90 | $size);
153
        }
154 3
        if ($size <= 0xffff) {
155 2
            return "\xdc".\chr($size >> 8).\chr($size);
156
        }
157
158 1
        return \pack('CN', 0xdd, $size);
159
    }
160
161 17
    public function packMap(array $map)
162
    {
163 17
        $size = \count($map);
164 17
        $data = self::packMapHeader($size);
165
166 17
        foreach ($map as $key => $val) {
167 17
            $data .= $this->pack($key);
168 17
            $data .= $this->pack($val);
169 17
        }
170
171 17
        return $data;
172
    }
173
174 17 View Code Duplication
    private static function packMapHeader($size)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
    {
176 17
        if ($size <= 0xf) {
177 14
            return \chr(0x80 | $size);
178
        }
179 3
        if ($size <= 0xffff) {
180 2
            return "\xde".\chr($size >> 8).\chr($size);
181
        }
182
183 1
        return \pack('CN', 0xdf, $size);
184
    }
185
186 25
    public function packStr($str)
187
    {
188 25
        $len = \strlen($str);
189
190 25
        if ($len < 32) {
191 18
            return \chr(0xa0 | $len).$str;
192
        }
193 7
        if ($len <= 0xff) {
194 4
            return "\xd9".\chr($len).$str;
195
        }
196 3
        if ($len <= 0xffff) {
197 2
            return "\xda".\chr($len >> 8).\chr($len).$str;
198
        }
199
200 1
        return \pack('CN', 0xdb, $len).$str;
201
    }
202
203 16
    public function packBin($str)
204
    {
205 16
        $len = \strlen($str);
206
207 16
        if ($len <= 0xff) {
208 14
            return "\xc4".\chr($len).$str;
209
        }
210 2
        if ($len <= 0xffff) {
211 1
            return "\xc5".\chr($len >> 8).\chr($len).$str;
212
        }
213
214 1
        return \pack('CN', 0xc6, $len).$str;
215
    }
216
217 10
    public function packExt(Ext $ext)
218
    {
219 10
        $type = $ext->getType();
220 10
        $data = $ext->getData();
221 10
        $len = \strlen($data);
222
223
        switch ($len) {
224 10
            case 1: return "\xd4".\chr($type).$data;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
225 8
            case 2: return "\xd5".\chr($type).$data;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
226 7
            case 4: return "\xd6".\chr($type).$data;
227 6
            case 8: return "\xd7".\chr($type).$data;
228 5
            case 16: return "\xd8".\chr($type).$data;
229
        }
230
231 4
        if ($len <= 0xff) {
232 2
            return "\xc7".\chr($len).\chr($type).$data;
233
        }
234 2
        if ($len <= 0xffff) {
235 1
            return \pack('CnC', 0xc8, $len, $type).$data;
236
        }
237
238 1
        return \pack('CNC', 0xc9, $len, $type).$data;
239
    }
240
241 3
    public function packNil()
242
    {
243 3
        return "\xc0";
244
    }
245
246 6
    public function packBool($val)
247
    {
248 6
        return $val ? "\xc3" : "\xc2";
249
    }
250
251 3
    public function packFloat($num)
252
    {
253 3
        return "\xcb".strrev(pack('d', $num));
254
    }
255
256 55
    public function packInt($num)
257
    {
258 55
        if ($num >= 0) {
259 40
            if ($num <= 0x7f) {
260 28
                return \chr($num);
261
            }
262 16
            if ($num <= 0xff) {
263 6
                return "\xcc".\chr($num);
264
            }
265 12
            if ($num <= 0xffff) {
266 6
                return "\xcd".\chr($num >> 8).\chr($num);
267
            }
268 7
            if ($num <= 0xffffffff) {
269 5
                return \pack('CN', 0xce, $num);
270
            }
271
272 3
            return self::packUint64(0xcf, $num);
273
        }
274
275 16
        if ($num >= -0x20) {
276 4
            return \chr(0xe0 | $num);
277
        }
278 12
        if ($num >= -0x80) {
279 3
            return "\xd0".\chr($num);
280
        }
281 9
        if ($num >= -0x8000) {
282 3
            return "\xd1".\chr($num >> 8).\chr($num);
283
        }
284 6
        if ($num >= -0x80000000) {
285 3
            return \pack('CN', 0xd2, $num);
286
        }
287
288 3
        return self::packUint64(0xd3, $num);
289
    }
290
291 6
    private static function packUint64($code, $num)
292
    {
293 6
        $hi = ($num & 0xffffffff00000000) >> 32;
294 6
        $lo = $num & 0x00000000ffffffff;
295
296 6
        return \pack('CNN', $code, $hi, $lo);
297
    }
298
}
299