Completed
Push — master ( 09c0d5...1233c7 )
by Eugene
05:50
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 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