Completed
Branch packer_type_detection (5412ac)
by Eugene
03:32
created

Packer::setTypeDetectionMode()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 2
nop 1
crap 4
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 91
    public function __construct($typeDetectionMode = null)
51
    {
52 91
        if (null !== $typeDetectionMode) {
53
            $this->setTypeDetectionMode($typeDetectionMode);
54
        }
55 91
    }
56
57
    /**
58
     * @param int $mode
59
     */
60 13
    public function setTypeDetectionMode($mode)
61
    {
62 13
        if ($mode > 0b1010 || $mode < 0 || 0b11 === ($mode & 0b11)) {
63 5
            throw new \InvalidArgumentException('Invalid type detection mode.');
64
        }
65
66 8
        $this->strDetectionMode = $mode & 0b0011;
67 8
        $this->arrDetectionMode = $mode & 0b1100;
68 8
    }
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 85
    public function pack($value)
87
    {
88 85
        if (\is_int($value)) {
89 47
            return $this->packInt($value);
90
        }
91 56
        if (\is_string($value)) {
92 29
            if (self::FORCE_STR === $this->strDetectionMode) {
93 3
                return $this->packStr($value);
94
            }
95 26
            if (self::FORCE_BIN === $this->strDetectionMode) {
96 3
                return $this->packBin($value);
97
            }
98 23
            return \preg_match(self::NON_UTF8_REGEX, $value)
99 23
                ? $this->packBin($value)
100 23
                : $this->packStr($value);
101
        }
102 37
        if (\is_array($value)) {
103 20
            if (self::FORCE_ARR === $this->arrDetectionMode) {
104 3
                return $this->packArray($value);
105
            }
106 17
            if (self::FORCE_MAP === $this->arrDetectionMode) {
107 3
                return $this->packMap($value);
108
            }
109 14
            return \array_values($value) === $value
110 14
                ? $this->packArray($value)
111 14
                : $this->packMap($value);
112
        }
113 21
        if (null === $value) {
114 3
            return $this->packNil();
115
        }
116 20
        if (\is_bool($value)) {
117 6
            return $this->packBool($value);
118
        }
119 15
        if (\is_double($value)) {
120 3
            return $this->packFloat($value);
121
        }
122 12
        if ($value instanceof Ext) {
123 9
            return $this->packExt($value);
124
        }
125
126 3
        if ($this->transformers && $transformer = $this->transformers->match($value)) {
127 1
            $ext = new Ext($transformer->getId(), $this->pack($transformer->transform($value)));
128
129 1
            return $this->packExt($ext);
130
        }
131
132 2
        throw new PackingFailedException($value, 'Unsupported type.');
133
    }
134
135 10
    public function packArray(array $array)
136
    {
137 10
        $size = \count($array);
138 10
        $data = self::packArrayHeader($size);
139
140 10
        foreach ($array as $val) {
141 9
            $data .= $this->pack($val);
142 10
        }
143
144 10
        return $data;
145
    }
146
147 10 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...
148
    {
149 10
        if ($size <= 0xf) {
150 7
            return \chr(0x90 | $size);
151
        }
152 3
        if ($size <= 0xffff) {
153 2
            return "\xdc".\chr($size >> 8).\chr($size);
154
        }
155
156 1
        return \pack('CN', 0xdd, $size);
157
    }
158
159 12
    public function packMap(array $map)
160
    {
161 12
        $size = \count($map);
162 12
        $data = self::packMapHeader($size);
163
164 12
        foreach ($map as $key => $val) {
165 12
            $data .= $this->pack($key);
166 12
            $data .= $this->pack($val);
167 12
        }
168
169 12
        return $data;
170
    }
171
172 12 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...
173
    {
174 12
        if ($size <= 0xf) {
175 9
            return \chr(0x80 | $size);
176
        }
177 3
        if ($size <= 0xffff) {
178 2
            return "\xde".\chr($size >> 8).\chr($size);
179
        }
180
181 1
        return \pack('CN', 0xdf, $size);
182
    }
183
184 20
    public function packStr($str)
185
    {
186 20
        $len = \strlen($str);
187
188 20
        if ($len < 32) {
189 13
            return \chr(0xa0 | $len).$str;
190
        }
191 7
        if ($len <= 0xff) {
192 4
            return "\xd9".\chr($len).$str;
193
        }
194 3
        if ($len <= 0xffff) {
195 2
            return "\xda".\chr($len >> 8).\chr($len).$str;
196
        }
197
198 1
        return \pack('CN', 0xdb, $len).$str;
199
    }
200
201 11
    public function packBin($str)
202
    {
203 11
        $len = \strlen($str);
204
205 11
        if ($len <= 0xff) {
206 9
            return "\xc4".\chr($len).$str;
207
        }
208 2
        if ($len <= 0xffff) {
209 1
            return "\xc5".\chr($len >> 8).\chr($len).$str;
210
        }
211
212 1
        return \pack('CN', 0xc6, $len).$str;
213
    }
214
215 10
    public function packExt(Ext $ext)
216
    {
217 10
        $type = $ext->getType();
218 10
        $data = $ext->getData();
219 10
        $len = \strlen($data);
220
221
        switch ($len) {
222 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...
223 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...
224 7
            case 4: return "\xd6".\chr($type).$data;
225 6
            case 8: return "\xd7".\chr($type).$data;
226 5
            case 16: return "\xd8".\chr($type).$data;
227
        }
228
229 4
        if ($len <= 0xff) {
230 2
            return "\xc7".\chr($len).\chr($type).$data;
231
        }
232 2
        if ($len <= 0xffff) {
233 1
            return \pack('CnC', 0xc8, $len, $type).$data;
234
        }
235
236 1
        return \pack('CNC', 0xc9, $len, $type).$data;
237
    }
238
239 3
    public function packNil()
240
    {
241 3
        return "\xc0";
242
    }
243
244 6
    public function packBool($val)
245
    {
246 6
        return $val ? "\xc3" : "\xc2";
247
    }
248
249 3
    public function packFloat($num)
250
    {
251 3
        return "\xcb".strrev(pack('d', $num));
252
    }
253
254 47
    public function packInt($num)
255
    {
256 47
        if ($num >= 0) {
257 32
            if ($num <= 0x7f) {
258 20
                return \chr($num);
259
            }
260 16
            if ($num <= 0xff) {
261 6
                return "\xcc".\chr($num);
262
            }
263 12
            if ($num <= 0xffff) {
264 6
                return "\xcd".\chr($num >> 8).\chr($num);
265
            }
266 7
            if ($num <= 0xffffffff) {
267 5
                return \pack('CN', 0xce, $num);
268
            }
269
270 3
            return self::packUint64(0xcf, $num);
271
        }
272
273 16
        if ($num >= -0x20) {
274 4
            return \chr(0xe0 | $num);
275
        }
276 12
        if ($num >= -0x80) {
277 3
            return "\xd0".\chr($num);
278
        }
279 9
        if ($num >= -0x8000) {
280 3
            return "\xd1".\chr($num >> 8).\chr($num);
281
        }
282 6
        if ($num >= -0x80000000) {
283 3
            return \pack('CN', 0xd2, $num);
284
        }
285
286 3
        return self::packUint64(0xd3, $num);
287
    }
288
289 6
    private static function packUint64($code, $num)
290
    {
291 6
        $hi = ($num & 0xffffffff00000000) >> 32;
292 6
        $lo = $num & 0x00000000ffffffff;
293
294 6
        return \pack('CNN', $code, $hi, $lo);
295
    }
296
}
297