Completed
Push — master ( 4dd7c8...9d1314 )
by kacper
02:20
created

JsonBinaryDecoderService::parseValueType()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 67
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 67
rs 6.6523
cc 8
eloc 40
nc 8
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
4
namespace MySQLReplication\JsonBinaryDecoder;
5
6
use MySQLReplication\BinaryDataReader\BinaryDataReader;
7
use MySQLReplication\BinaryDataReader\Exception\BinaryDataReaderException;
8
9
/**
10
 * Class JsonBinaryDecoderService
11
 * @package MySQLReplication\JsonBinaryDecoder
12
 * @see https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.cc
13
 * @see https://github.com/shyiko/mysql-binlog-connector-java/blob/master/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinary.java
14
 */
15
class JsonBinaryDecoderService
16
{
17
    const SMALL_OBJECT = 0;
18
    const LARGE_OBJECT = 1;
19
    const SMALL_ARRAY = 2;
20
    const LARGE_ARRAY = 3;
21
    const LITERAL = 4;
22
    const INT16 = 5;
23
    const UINT16 = 6;
24
    const INT32 = 7;
25
    const UINT32 = 8;
26
    const INT64 = 9;
27
    const UINT64 = 10;
28
    const DOUBLE = 11;
29
    const STRING = 12;
30
    const OPAQUE = 15;
31
32
    /**
33
     * @var BinaryDataReader
34
     */
35
    private $binaryDataReader;
36
    /**
37
     * @var JsonBinaryDecoderFormatter
38
     */
39
    private $jsonBinaryDecoderFormatter;
40
41
    /**
42
     * JsonBinaryDecoderService constructor.
43
     * @param BinaryDataReader $binaryDataReader
44
     * @param JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter
45
     */
46
    public function __construct(BinaryDataReader $binaryDataReader, JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter)
47
    {
48
        $this->binaryDataReader = $binaryDataReader;
49
        $this->jsonBinaryDecoderFormatter = $jsonBinaryDecoderFormatter;
50
    }
51
52
    /**
53
     * @return string
54
     * @throws JsonBinaryDecoderException
55
     * @throws BinaryDataReaderException
56
     */
57
    public function parseToString()
58
    {
59
        $this->parseJson($this->binaryDataReader->readUInt8());
60
61
        return $this->jsonBinaryDecoderFormatter->getJsonString();
62
    }
63
64
    /**
65
     * @return int
66
     * @throws BinaryDataReaderException
67
     */
68
    private function readVariableInt()
69
    {
70
        $length = $this->binaryDataReader->getBinaryDataLength();
71
        $len = 0;
72
        for ($i = 0; $i < $length; $i++)
73
        {
74
            $size = $this->binaryDataReader->readUInt8();
75
            // Get the next 7 bits of the length.
76
            $len |= ($size & 127) << (7 * $i);
77
            if (($size & 128) === 0)
78
            {
79
                // This was the last byte. Return successfully.
80
                return $len;
81
            }
82
        }
83
84
        return $len;
85
    }
86
87
    /**
88
     * @param int $type
89
     * @throws JsonBinaryDecoderException
90
     * @throws BinaryDataReaderException
91
     */
92
    private function parseJson($type)
93
    {
94
        if (self::SMALL_OBJECT === $type)
95
        {
96
            $this->parseObject(BinaryDataReader::UNSIGNED_SHORT_LENGTH);
97
        }
98
        else if (self::LARGE_OBJECT === $type)
99
        {
100
            $this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH);
101
        }
102
        else if (self::SMALL_ARRAY === $type)
103
        {
104
            $this->parseArray(BinaryDataReader::UNSIGNED_SHORT_LENGTH);
105
        }
106
        else if (self::LARGE_ARRAY === $type)
107
        {
108
            $this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH);
109
        }
110
        else
111
        {
112
            $this->parseScalar($type);
113
        }
114
    }
115
116
    private function parseObject($intSize)
117
    {
118
        $elementCount = $this->binaryDataReader->readUIntBySize($intSize);
119
        $size = $this->binaryDataReader->readUIntBySize($intSize);
120
121
        // Read each key-entry, consisting of the offset and length of each key ...
122
        $keyLengths = [];
123
        for ($i = 0; $i !== $elementCount; ++$i)
124
        {
125
            $this->binaryDataReader->readUIntBySize($intSize); // $keyOffset unused
126
            $keyLengths[$i] = $this->binaryDataReader->readUInt16();
127
        }
128
129
        $entries = [];
130
        for ($i = 0; $i !== $elementCount; ++$i)
131
        {
132
            $entries[$i] = $this->parseValueType($size, $intSize);
133
        }
134
135
        // Read each key ...
136
        $keys = [];
137
        for ($i = 0; $i !== $elementCount; ++$i)
138
        {
139
            $keys[$i] = $this->binaryDataReader->read($keyLengths[$i]);
140
        }
141
142
        $this->jsonBinaryDecoderFormatter->formatBeginObject();
143
144 View Code Duplication
        for ($i = 0; $i !== $elementCount; ++$i)
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...
145
        {
146
            if ($i !== 0)
147
            {
148
                $this->jsonBinaryDecoderFormatter->formatNextEntry();
149
            }
150
151
            $this->jsonBinaryDecoderFormatter->formatName($keys[$i]);
152
153
            /* @var JsonBinaryDecoderValue[] $entries */
154
            $this->assignValues($entries[$i]);
155
        }
156
157
        $this->jsonBinaryDecoderFormatter->formatEndObject();
158
    }
159
160
    /**
161
     * @param int $numBytes
162
     * @param int $intSize
163
     * @return JsonBinaryDecoderValue
164
     * @throws BinaryDataReaderException
165
     * @throws \LengthException
166
     */
167
    private function parseValueType($numBytes, $intSize)
168
    {
169
        $type = $this->binaryDataReader->readInt8();
170
171
        if (self::LITERAL === $type)
172
        {
173
            return new JsonBinaryDecoderValue(
174
                true,
175
                $this->readLiteral(),
176
                $type
177
            );
178
        }
179
        else if (self::INT16 === $type)
180
        {
181
            return new JsonBinaryDecoderValue(
182
                true,
183
                $this->binaryDataReader->readInt16(),
184
                $type
185
            );
186
        }
187
        else if (self::UINT16 === $type)
188
        {
189
            return new JsonBinaryDecoderValue(
190
                true,
191
                $this->binaryDataReader->readUInt16(),
192
                $type
193
            );
194
        }
195
        else if (BinaryDataReader::UNSIGNED_INT32_LENGTH === $intSize)
196
        {
197
            if (self::INT32 === $type)
198
            {
199
                return new JsonBinaryDecoderValue(
200
                    true,
201
                    $this->binaryDataReader->readInt32(),
202
                    $type
203
                );
204
            }
205
            else if (self::UINT32 === $type)
206
            {
207
                return new JsonBinaryDecoderValue(
208
                    true,
209
                    $this->binaryDataReader->readUInt32(),
210
                    $type
211
                );
212
            }
213
        }
214
        else
215
        {
216
            $offset = $this->binaryDataReader->readUIntBySize($intSize);
217
            if ($offset > $numBytes)
218
            {
219
                throw new \LengthException(
220
                    'The offset for the value in the JSON binary document is ' .
221
                    $offset .
222
                    ', which is larger than the binary form of the JSON document (' .
223
                    $numBytes . ' bytes)'
224
                );
225
            }
226
227
            return new JsonBinaryDecoderValue(
228
                false,
229
                null,
230
                $type
231
            );
232
        }
233
    }
234
235
    /**
236
     * @return bool|null
237
     * @throws BinaryDataReaderException
238
     */
239
    private function readLiteral()
240
    {
241
        $literal = ord($this->binaryDataReader->read(2));
242
        if (0 === $literal)
243
        {
244
            return null;
245
        }
246
        else if (1 === $literal)
247
        {
248
            return true;
249
        }
250
        else if (2 === $literal)
251
        {
252
            return false;
253
        }
254
    }
255
256
    /**
257
     * @param JsonBinaryDecoderValue $jsonBinaryDecoderValue
258
     * @throws JsonBinaryDecoderException
259
     * @throws BinaryDataReaderException
260
     */
261
    private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue)
262
    {
263
        if (false === $jsonBinaryDecoderValue->isIsResolved())
264
        {
265
            $this->parseJson($jsonBinaryDecoderValue->getType());
266
        }
267
        else
268
        {
269
            if (null === $jsonBinaryDecoderValue->getValue())
270
            {
271
                $this->jsonBinaryDecoderFormatter->formatValueNull();
272
            }
273
            elseif (is_bool($jsonBinaryDecoderValue->getValue()))
274
            {
275
                $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue());
276
            }
277
            elseif (is_numeric($jsonBinaryDecoderValue->getValue()))
278
            {
279
                $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue());
280
            }
281
        }
282
    }
283
284
    private function parseArray($size)
285
    {
286
        $numElements = $this->binaryDataReader->readUInt16();
287
        $numBytes = $this->binaryDataReader->readUInt16();
288
289
        $entries = [];
290
        for ($i = 0; $i !== $numElements; ++$i)
291
        {
292
            $entries[$i] = $this->parseValueType($numBytes, $size);
293
        }
294
295
        $this->jsonBinaryDecoderFormatter->formatBeginArray();
296
297 View Code Duplication
        for ($i = 0; $i !== $numElements; ++$i)
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...
298
        {
299
            if ($i !== 0)
300
            {
301
                $this->jsonBinaryDecoderFormatter->formatNextEntry();
302
            }
303
304
            /* @var JsonBinaryDecoderValue[] $entries */
305
            $this->assignValues($entries[$i]);
306
        }
307
308
        $this->jsonBinaryDecoderFormatter->formatEndArray();
309
    }
310
311
    private function parseBoolean()
312
    {
313
        $r = $this->readLiteral();
314
        if (null === $r)
315
        {
316
            $this->jsonBinaryDecoderFormatter->formatValueNull();
317
        }
318
        else
319
        {
320
            $this->jsonBinaryDecoderFormatter->formatValueBool($r);
321
        }
322
    }
323
324
    private function parseScalar($type)
325
    {
326
        if (self::LITERAL === $type)
327
        {
328
            $this->parseBoolean();
329
        }
330
        else if (self::INT16 === $type)
331
        {
332
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16());
333
        }
334
        else if (self::INT32 === $type)
335
        {
336
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32());
337
        }
338
        else if (self::INT64 === $type)
339
        {
340
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64());
341
        }
342
        else if (self::UINT16 === $type)
343
        {
344
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16());
345
        }
346
        else if (self::UINT64 === $type)
347
        {
348
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64());
349
        }
350
        else if (self::DOUBLE === $type)
351
        {
352
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble());
353
        }
354
        else if (self::STRING === $type)
355
        {
356
            $this->jsonBinaryDecoderFormatter->formatValue(
357
                $this->binaryDataReader->read($this->readVariableInt())
358
            );
359
        }
360
        /**
361
         * else if (self::OPAQUE === $type)
362
         * {
363
         *
364
         * }
365
         */
366
        else
367
        {
368
            throw new JsonBinaryDecoderException(
369
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type,
370
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE
371
            );
372
        }
373
    }
374
}