Completed
Branch transformers (fd9f11)
by Eugene
01:40
created

Packer   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 252
Duplicated Lines 5.56 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 99.2%

Importance

Changes 0
Metric Value
wmc 60
c 0
b 0
f 0
lcom 1
cbo 4
dl 14
loc 252
ccs 124
cts 125
cp 0.992
rs 6.0975

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A registerTransformer() 0 4 1
C pack() 0 49 18
A packArray() 7 18 4
A packMap() 7 19 4
A packStr() 0 16 4
A packBin() 0 13 3
B packExt() 0 21 8
A packNil() 0 4 1
A packBool() 0 4 2
A packFloat32() 0 4 1
A packFloat64() 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\InvalidOptionException;
15
use MessagePack\Exception\PackingFailedException;
16
use MessagePack\TypeTransformer\Packable;
17
18
class Packer
19
{
20
    const UTF8_REGEX = '/\A(?:
21
          [\x00-\x7F]++                      # ASCII
22
        | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
23
        |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
24
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
25
        |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
26
        |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
27
        | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
28
        |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
29
        )*+\z/x';
30
31
    private $isDetectStrBin;
32
    private $isForceStr;
33
    private $isDetectArrMap;
34
    private $isForceArr;
35
    private $isForceFloat32;
36
37
    /**
38
     * @var Packable[]
39
     */
40
    private $transformers = [];
41
42
    /**
43
     * @param PackOptions|int|null $options
44
     *
45
     * @throws InvalidOptionException
46
     */
47 109
    public function __construct($options = null)
48
    {
49 109
        if (!$options instanceof PackOptions) {
50 109
            $options = PackOptions::fromBitmask($options);
51
        }
52
53 109
        $this->isDetectStrBin = $options->isDetectStrBinMode();
54 109
        $this->isForceStr = $options->isForceStrMode();
55 109
        $this->isDetectArrMap = $options->isDetectArrMapMode();
56 109
        $this->isForceArr = $options->isForceArrMode();
57 109
        $this->isForceFloat32 = $options->isForceFloat32Mode();
58 109
    }
59
60 1
    public function registerTransformer(Packable $transformer)
61
    {
62 1
        $this->transformers[] = $transformer;
63 1
    }
64
65 98
    public function pack($value)
66
    {
67 98
        if (\is_int($value)) {
68 52
            return $this->packInt($value);
69
        }
70 69
        if (\is_string($value)) {
71 33
            if ($this->isDetectStrBin) {
72 27
                return \preg_match(self::UTF8_REGEX, $value)
73 19
                    ? $this->packStr($value)
74 27
                    : $this->packBin($value);
75
            }
76
77 6
            return $this->isForceStr ? $this->packStr($value) : $this->packBin($value);
78
        }
79 46
        if (\is_array($value)) {
80 26
            if ($this->isDetectArrMap) {
81 19
                return \array_values($value) === $value
82 10
                    ? $this->packArray($value)
83 19
                    : $this->packMap($value);
84
            }
85
86 7
            return $this->isForceArr ? $this->packArray($value) : $this->packMap($value);
87
        }
88 24
        if (null === $value) {
89 3
            return $this->packNil();
90
        }
91 23
        if (\is_bool($value)) {
92 6
            return $this->packBool($value);
93
        }
94 18
        if (\is_float($value)) {
95 6
            return $this->isForceFloat32
96 1
                ? $this->packFloat32($value)
97 6
                : $this->packFloat64($value);
98
        }
99 12
        if ($value instanceof Ext) {
100 9
            return $this->packExt($value->type, $value->data);
101
        }
102 3
        if ($this->transformers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->transformers of type MessagePack\TypeTransformer\Packable[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
103 1
            foreach ($this->transformers as $transformer) {
104 1
                if (null === $packed = $transformer->pack($this, $value)) {
105
                    continue;
106
                }
107
108 1
                return $packed;
109
            }
110
        }
111
112 2
        throw new PackingFailedException($value, 'Unsupported type.');
113
    }
114
115 13
    public function packArray(array $array)
116
    {
117 13
        $size = \count($array);
118
119 13 View Code Duplication
        if ($size <= 0xf) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
120 10
            $data = \chr(0x90 | $size);
121 3
        } elseif ($size <= 0xffff) {
122 2
            $data = "\xdc".\chr($size >> 8).\chr($size);
123
        } else {
124 1
            $data = \pack('CN', 0xdd, $size);
125
        }
126
127 13
        foreach ($array as $val) {
128 12
            $data .= $this->pack($val);
129
        }
130
131 13
        return $data;
132
    }
133
134 15
    public function packMap(array $map)
135
    {
136 15
        $size = \count($map);
137
138 15 View Code Duplication
        if ($size <= 0xf) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
139 12
            $data = \chr(0x80 | $size);
140 3
        } elseif ($size <= 0xffff) {
141 2
            $data = "\xde".\chr($size >> 8).\chr($size);
142
        } else {
143 1
            $data = \pack('CN', 0xdf, $size);
144
        }
145
146 15
        foreach ($map as $key => $val) {
147 15
            $data .= $this->pack($key);
148 15
            $data .= $this->pack($val);
149
        }
150
151 15
        return $data;
152
    }
153
154 22
    public function packStr($str)
155
    {
156 22
        $len = \strlen($str);
157
158 22
        if ($len < 32) {
159 15
            return \chr(0xa0 | $len).$str;
160
        }
161 7
        if ($len <= 0xff) {
162 4
            return "\xd9".\chr($len).$str;
163
        }
164 3
        if ($len <= 0xffff) {
165 2
            return "\xda".\chr($len >> 8).\chr($len).$str;
166
        }
167
168 1
        return \pack('CN', 0xdb, $len).$str;
169
    }
170
171 13
    public function packBin($str)
172
    {
173 13
        $len = \strlen($str);
174
175 13
        if ($len <= 0xff) {
176 11
            return "\xc4".\chr($len).$str;
177
        }
178 2
        if ($len <= 0xffff) {
179 1
            return "\xc5".\chr($len >> 8).\chr($len).$str;
180
        }
181
182 1
        return \pack('CN', 0xc6, $len).$str;
183
    }
184
185 9
    public function packExt($type, $data)
186
    {
187 9
        $len = \strlen($data);
188
189 9
        switch ($len) {
190 9
            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...
191 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...
192 7
            case 4: return "\xd6".\chr($type).$data;
193 6
            case 8: return "\xd7".\chr($type).$data;
194 5
            case 16: return "\xd8".\chr($type).$data;
195
        }
196
197 4
        if ($len <= 0xff) {
198 2
            return "\xc7".\chr($len).\chr($type).$data;
199
        }
200 2
        if ($len <= 0xffff) {
201 1
            return \pack('CnC', 0xc8, $len, $type).$data;
202
        }
203
204 1
        return \pack('CNC', 0xc9, $len, $type).$data;
205
    }
206
207 3
    public function packNil()
208
    {
209 3
        return "\xc0";
210
    }
211
212 6
    public function packBool($val)
213
    {
214 6
        return $val ? "\xc3" : "\xc2";
215
    }
216
217 3
    public function packFloat32($num)
218
    {
219 3
        return "\xca".\strrev(\pack('f', $num));
220
    }
221
222 5
    public function packFloat64($num)
223
    {
224 5
        return "\xcb".\strrev(\pack('d', $num));
225
    }
226
227 52
    public function packInt($num)
228
    {
229 52
        if ($num >= 0) {
230 37
            if ($num <= 0x7f) {
231 25
                return \chr($num);
232
            }
233 16
            if ($num <= 0xff) {
234 6
                return "\xcc".\chr($num);
235
            }
236 12
            if ($num <= 0xffff) {
237 6
                return "\xcd".\chr($num >> 8).\chr($num);
238
            }
239 7
            if ($num <= 0xffffffff) {
240 5
                return \pack('CN', 0xce, $num);
241
            }
242
243 3
            return self::packUint64(0xcf, $num);
244
        }
245
246 16
        if ($num >= -0x20) {
247 4
            return \chr(0xe0 | $num);
248
        }
249 12
        if ($num >= -0x80) {
250 3
            return "\xd0".\chr($num);
251
        }
252 9
        if ($num >= -0x8000) {
253 3
            return "\xd1".\chr($num >> 8).\chr($num);
254
        }
255 6
        if ($num >= -0x80000000) {
256 3
            return \pack('CN', 0xd2, $num);
257
        }
258
259 3
        return self::packUint64(0xd3, $num);
260
    }
261
262 6
    private static function packUint64($code, $num)
263
    {
264 6
        $hi = ($num & 0xffffffff00000000) >> 32;
265 6
        $lo = $num & 0x00000000ffffffff;
266
267 6
        return \pack('CNN', $code, $hi, $lo);
268
    }
269
}
270