Serializer::unserialize_array_key_value()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.3332

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 8
cts 12
cp 0.6667
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 12
nc 3
nop 2
crap 3.3332
1
<?php
2
3
    namespace NokitaKaze\Serializer;
4
5
    class Serializer {
6
        const TYPE_INT = 0;
7
        const TYPE_DOUBLE = 1;
8
        const TYPE_STRING = 2;
9
        const TYPE_ARRAY_INDEX = 3;
10
        const TYPE_ARRAY_KEY_VALUE = 4;
11
        const TYPE_OBJECT_KEY_VALUE = 5;
12
        const TYPE_SERIALIZABLE = ISerializable::TYPE;
13
        const TYPE_RESOURCE = 7;
14
        const TYPE_BOOLEAN = 7;
15
        const TYPE_NULL = 8;
16
17
        const DEFAULT_DEPTH = 255;
18
19
        /**
20
         * @param mixed   $data
21
         * @param integer $depth
22
         *
23
         * @return string
24
         */
25 23
        public static function serialize($data, $depth = self::DEFAULT_DEPTH) {
26 23
            return json_encode(static::serialize_to_variant($data, $depth));
27
        }
28
29
        /**
30
         * @param mixed   $text
31
         * @param boolean $is_valid
32
         * @param boolean $safe
33
         *
34
         * @return mixed
35
         */
36 28
        public static function unserialize($text, &$is_valid, $safe = true) {
37 28
            $is_valid = true;
38 28
            if ($text === 'null') {
39 1
                return null;
40
            }
41 27
            $data = json_decode($text);
42 27
            if (is_null($data)) {
43 1
                $is_valid = false;
44
45 1
                return null;
46
            }
47
48
            try {
49 26
                $object = static::unserialize_variant_to_value($data, $safe);
50 4
            } catch (UnserializeException $e) {
51 4
                $is_valid = false;
52
53 4
                return null;
54
            }
55
56 22
            return $object;
57
        }
58
59
        /**
60
         * @param mixed   $data
61
         * @param integer $depth
62
         *
63
         * @return Variant|object|boolean|null
64
         */
65 23
        public static function serialize_to_variant($data, $depth = self::DEFAULT_DEPTH) {
66 23
            if ($depth < 0) {
67 1
                return null;
68
            }
69 23
            if (is_double($data)) {
70 8
                return static::serialize_double($data);
71 17
            } elseif (is_int($data)) {
72 4
                return static::serialize_int($data);
73 16
            } elseif (is_string($data)) {
74 5
                return static::serialize_string($data);
75 14
            } elseif (is_array($data) and static::is_indexed_array($data)) {
76 4
                return static::serialize_indexed_array($data, $depth - 1);
77 12
            } elseif (is_array($data)) {
78 2
                return static::serialize_array_key_value($data, $depth - 1);
79 11
            } elseif (is_object($data) and ($data instanceof ISerializable)) {
80
                /**
81
                 * @var ISerializable $data
82
                 */
83 3
                return $data->ns_serialize();
84 8
            } elseif (is_object($data)) {
85 4
                return static::serialize_object_key_value($data);
86 4
            } elseif (is_resource($data)) {
87
                // @todo Придумать что делать с ресурсами
88
                return null;
89 4
            } elseif (is_bool($data)) {
90 3
                return $data;
91 2
            } elseif (is_null($data)) {
92 2
                return null;
93
            } else {
94
                return null;
95
            }
96
        }
97
98
        protected static $_binary_codes = [
99
            0, 1, 2, 3, 4, 5, 6, 7, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 128, 129, 130,
100
            131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
101
            154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176,
102
            177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
103
            200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
104
            223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
105
            246, 247, 248, 249, 250, 251, 252, 253, 254,
106
        ];
107
108
        protected static $_binary_codes_flip = null;
109
110
        /**
111
         * @param string $value
112
         *
113
         * @return boolean
114
         */
115 19
        public static function is_binary_string($value) {
116 19
            if (is_null(self::$_binary_codes_flip)) {
117 1
                self::$_binary_codes_flip = array_flip(self::$_binary_codes);
118
            }
119
120
            // @todo Оптимизировать через > & <
121 19
            for ($i = 0; $i < strlen($value); $i++) {
122 19
                $ord = ord(substr($value, $i, 1));
123 19
                if (isset(self::$_binary_codes_flip[$ord])) {
124 1
                    return true;
125
                }
126
            }
127
128 19
            return false;
129
        }
130
131
        /**
132
         * @param double $value
133
         *
134
         * @return Variant|object
135
         */
136 8
        public static function serialize_double($value) {
137
            return (object) [
138 8
                'type' => self::TYPE_DOUBLE,
139 8
                'value' => serialize($value),
140
            ];
141
        }
142
143
        /**
144
         * @param integer $value
145
         *
146
         * @return Variant|object
147
         */
148 4
        public static function serialize_int($value) {
149
            return (object) [
150 4
                'type' => self::TYPE_INT,
151 4
                'value' => $value,
152
            ];
153
        }
154
155
        /**
156
         * @param array   $value
157
         * @param integer $depth
158
         *
159
         * @return Variant|object
160
         */
161 4
        public static function serialize_indexed_array(array $value, $depth) {
162 4
            $output = [];
163 4
            foreach ($value as $sub_value) {
164 3
                $output[] = self::serialize_to_variant($sub_value, $depth - 1);
165
            }
166
167
            return (object) [
168 4
                'type' => self::TYPE_ARRAY_INDEX,
169 4
                'value' => $output,
170
            ];
171
        }
172
173
        /**
174
         * @param array   $value
175
         * @param integer $depth
176
         *
177
         * @return Variant|object
178
         */
179 2
        public static function serialize_array_key_value(array $value, $depth) {
180 2
            $output = [];
181 2
            $keys = [];
182 2
            foreach ($value as $key => $sub_value) {
183 2
                $keys[] = self::serialize_to_variant($key, $depth - 1);
184 2
                $output[] = self::serialize_to_variant($sub_value, $depth - 1);
185
            }
186
187
            return (object) [
188 2
                'type' => self::TYPE_ARRAY_KEY_VALUE,
189 2
                'value' => $output,
190 2
                'keys' => $keys,
191
            ];
192
        }
193
194
        /**
195
         * @param \stdClass|object $value
196
         *
197
         * @return Variant|object
198
         */
199 4
        public static function serialize_object_key_value($value) {
200 4
            $output = [];
201 4
            $keys = [];
202
203 4
            foreach (get_object_vars($value) as $key => $value) {
204 1
                $keys[] = self::serialize_to_variant($key);
205 1
                $output[] = self::serialize_to_variant($value);
206
            }
207
208
            return (object) [
209 4
                'type' => self::TYPE_OBJECT_KEY_VALUE,
210 4
                'value' => $output,
211 4
                'keys' => $keys,
212
            ];
213
        }
214
215
        /**
216
         * @param string $value
217
         *
218
         * @return Variant|object
219
         */
220 5
        public static function serialize_string($value) {
221
            return (object) [
222 5
                'type' => self::TYPE_STRING,
223 5
                'value' => base64_encode(gzcompress($value)),
224
            ];
225
        }
226
227
        /**
228
         * @param array $input
229
         *
230
         * @return boolean
231
         */
232 6
        public static function is_indexed_array(array $input) {
233 6
            if (empty($input)) {
234 1
                return true;
235
            }
236
237 5
            $keys = array_keys($input);
238 5
            for ($i = 0; $i < count($keys); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
239 5
                if ($keys[$i] !== $i) {
240 2
                    return false;
241
                }
242
            }
243
244 3
            return true;
245
        }
246
247
        /**
248
         * @param \stdClass|Variant $variant
249
         *
250
         * @return string
251
         */
252 5
        public static function unserialize_string($variant) {
253 5
            return gzuncompress(base64_decode($variant->value));
254
        }
255
256
        /**
257
         * @param \stdClass|Variant $variant
258
         * @param boolean           $safe
259
         *
260
         * @return array
261
         * @throws UnserializeException
262
         */
263 4
        public static function unserialize_array_index($variant, $safe) {
264 4
            $output = [];
265 4
            foreach ($variant->value as $sub_value) {
266 3
                $output[] = self::unserialize_variant_to_value($sub_value, $safe);
267
            }
268
269 4
            return $output;
270
        }
271
272
        /**
273
         * @param \stdClass|KeyValueVariant $variant
274
         * @param boolean                   $safe
275
         *
276
         * @return array
277
         * @throws UnserializeException
278
         */
279 6
        public static function unserialize_array_key_value($variant, $safe) {
280 6
            $count = count($variant->keys);
281 6
            if ($count != count($variant->value)) {
282
                throw new UnserializeException(sprintf('Array keys count (%d) does not equal value count (%d)',
283
                    $count,
284
                    count($variant->value)
285
                ), 2);
286
            }
287
288 6
            $output = [];
289 6
            for ($i = 0; $i < count($variant->keys); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
290 3
                $output[self::unserialize_variant_to_value($variant->keys[$i], $safe)]
291 3
                    = self::unserialize_variant_to_value($variant->value[$i], $safe);
292
            }
293
294 6
            return $output;
295
        }
296
297
        /**
298
         * @param SerializableVariant|boolean|null|object $variant
299
         * @param boolean                                 $safe
300
         *
301
         * @return mixed
302
         * @throws UnserializeException
303
         */
304 6
        public static function unserialize_object($variant, $safe) {
305 6
            if (!isset($variant->class_FQDN)) {
306 1
                throw new UnserializeException('Variant does not contain field with FQDN', 3);
307
            }
308 5
            if (!class_exists($variant->class_FQDN)) {
309 1
                throw new UnserializeException(sprintf('Class "%s" does not exist', $variant->class_FQDN), 4);
310
            }
311 4
            if (!is_subclass_of($variant->class_FQDN, 'NokitaKaze\\Serializer\\ISerializable')) {
312 1
                throw new UnserializeException(sprintf('Class "%s" does not implement ISerializable', $variant->class_FQDN), 5);
313
            }
314 3
            if ($safe and !is_subclass_of($variant->class_FQDN, 'NokitaKaze\\Serializer\\ISafeSerializable')) {
315
                // Не поддерживает safe-десериализацию
316 1
                return null;
317
            }
318
319
            /**
320
             * @var ISerializable $class
321
             */
322 2
            $class = $variant->class_FQDN;
323
324 2
            return $class::ns_unserialize($variant);
325
        }
326
327
        /**
328
         * @param Variant|boolean|null|object $variant
329
         * @param boolean                     $safe
330
         *
331
         * @return mixed
332
         * @throws UnserializeException
333
         */
334 26
        public static function unserialize_variant_to_value($variant, $safe) {
335 26
            if (is_null($variant)) {
336 2
                return null;
337 26
            } elseif (is_bool($variant)) {
338 3
                return $variant;
339
            } else {
340 26
                switch ($variant->type) {
341 26
                    case self::TYPE_INT:
342 4
                        return $variant->value;
343 25
                    case self::TYPE_DOUBLE:
344 8
                        return \unserialize($variant->value);
345 19
                    case self::TYPE_STRING:
346 5
                        return self::unserialize_string($variant);
347 17
                    case self::TYPE_ARRAY_INDEX:
348 4
                        return self::unserialize_array_index($variant, $safe);
349 13
                    case self::TYPE_ARRAY_KEY_VALUE:
350 2
                        return self::unserialize_array_key_value($variant, $safe);
351 11
                    case self::TYPE_OBJECT_KEY_VALUE:
352 4
                        return (object) self::unserialize_array_key_value($variant, $safe);
353 7
                    case self::TYPE_SERIALIZABLE:
354 6
                        return self::unserialize_object($variant, $safe);
355
                    default:
356 1
                        throw new UnserializeException('Malformed type '.$variant->type, 1);
357
                }
358
            }
359
        }
360
361
    }
362
363
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...