Passed
Push — master ( 64683c...c493bc )
by Nikita
04:07 queued 10s
created

BinnAbstract::isArrayObject()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Knik\Binn;
4
5
abstract class BinnAbstract
6
{
7
    // Consts from original C++ Library
8
    const BINN_LIST         = 0xE0;
9
    const BINN_MAP          = 0xE1;
10
    const BINN_OBJECT       = 0xE2;
11
12
    const BINN_UINT8        = 0x20;
13
    const BINN_INT8         = 0x21;
14
    const BINN_UINT16       = 0x40;
15
    const BINN_INT16        = 0x41;
16
    const BINN_UINT32       = 0x60;
17
    const BINN_INT32        = 0x61;
18
    const BINN_UINT64       = 0x80;
19
    const BINN_INT64        = 0x81;
20
    const BINN_STRING       = 0xA0;
21
22
    const BINN_FLOAT32      = 0x62;  // (DWORD)
23
    const BINN_FLOAT64      = 0x82;  // (QWORD)
24
    const BINN_FLOAT        = self::BINN_FLOAT32;
25
26
    const BINN_BOOL         = 0x80061;
27
28
    const BINN_STORAGE_NOBYTES      = 0x00;
29
    const BINN_STORAGE_BYTE         = 0x20;  //  8 bits
30
    const BINN_STORAGE_WORD         = 0x40;  // 16 bits -- the endianess (byte order) is automatically corrected
31
    const BINN_STORAGE_DWORD        = 0x60;  // 32 bits -- the endianess (byte order) is automatically corrected
32
    const BINN_STORAGE_QWORD        = 0x80;  // 64 bits -- the endianess (byte order) is automatically corrected
33
    const BINN_STORAGE_STRING       = 0xA0;  // Are stored with null termination
34
    const BINN_STORAGE_BLOB         = 0xC0;
35
    const BINN_STORAGE_CONTAINER    = 0xE0;
36
37
    const BINN_NULL                 = 0x00;
38
    const BINN_TRUE                 = 0x01;
39
    const BINN_FALSE                = 0x02;
40
41
    const UINT8_MAX                 = 255;
42
    const UINT16_MAX                = 65535;
43
    const UINT32_MAX                = 4294967295;
44
    const UINT64_MAX                = 18446744073709551615;
45
46
    const INT8_MIN                  = -128;
47
    const INT8_MAX                  = 127;
48
    const INT16_MIN                 = -32768;
49
    const INT16_MAX                 = 32767;
50
    const INT32_MIN                 = -2147483648;
51
    const INT32_MAX                 = 2147483647;
52
    const INT64_MIN                 = -9223372036854775808;
53
    const INT64_MAX                 = 9223372036854775807;
54
55
    const BINN_STORAGE_MASK         = 0xE0;
56
    const BINN_TYPE_MASK            = 0x0F;
57
58
    const MIN_BINN_SIZE             = 3;
59
60
    // PHP Library consts
61
    const KEY_TYPE                 = 0;
62
    const KEY_VAL                  = 1;
63
    const KEY_SIZE                 = 2;
64
    const KEY_KEY                  = 3;
65
66
    /**
67
     * Binn object type: self::BINN_LIST, self::BINN_MAP, self::BINN_OBJECT
68
     *
69
     * @var int $binnType
70
     * @access protected
71
     */
72
    protected $binnType = self::BINN_NULL;
73
74
    /**
75
     * @var string
76
     */
77
    protected $binnClass = null;
78
79
    /**
80
     * Count elements in object
81
     *
82
     * @var int
83
     * @access protected
84
     */
85
    protected $count        = 0;
86
87
    /**
88
     * Data size in bytes
89
     *
90
     * @var int
91
     * @access protected
92
     */
93
    protected $dataSize    = 0;
94
95
    /**
96
     * Meta size in bytes
97
     *
98
     * @var int
99
     */
100
    protected $metaSize    = self::MIN_BINN_SIZE;
101
102
    /**
103
     * Size bin string in bytes
104
     *
105
     * @var int
106
     * @access protected
107
     */
108
    protected $size         = 0;
109
110
    /**
111
     * Bin string
112
     *
113
     * @var string
114
     * @access protected
115
     */
116
    protected $binnString     = "";
117
118
    /**
119
     * Object elements
120
     *
121
     * @var array
122
     * @access protected
123
     */
124
    protected $binnArr = [];
125
126
    /**
127
     * @var array
128
     *
129
     * Associations container int with container classes
130
     *
131
     * Example values:
132
     * [
133
     *  0xE0 => \Knik\Binn\BinnList::class,
134
     *  0xE1 => \Knik\Binn\BinnMap::class,
135
     *  0xE2 => \Knik\Binn\BinnObject::class,
136
     * ]
137
     */
138
    protected $containersClasses = [
139
        self::BINN_LIST     => \Knik\Binn\BinnList::class,
140
        self::BINN_MAP      => \Knik\Binn\BinnMap::class,
141
        self::BINN_OBJECT   => \Knik\Binn\BinnObject::class,
142
    ];
143
144
    /**
145
     * @param $containersClasses
146
     */
147 20
    public function setContainersClasses($containersClasses)
148
    {
149 20
        $this->containersClasses = $containersClasses;
150 20
    }
151
152
    /**
153
     * Get 4 bytes packed size. Add cut bit.
154
     *
155
     * @param int $intVal
156
     * @return string
157
     */
158 12
    protected function getInt32Binsize($intVal = 0)
159
    {
160 12
        $intVal = ($intVal | (1 << 31)); // Add bit
161 12
        return $this->pack(self::BINN_UINT32, $intVal);
162
    }
163
164
    /**
165
     * Detect value type
166
     *
167
     * @param mixed $value
168
     * @return int
169
     */
170 31
    protected function detectType($value)
171
    {
172 31
        if (is_bool($value)) {
173 6
            return $value ? self::BINN_TRUE : self::BINN_FALSE;
174
        }
175
176 31
        if (is_string($value)) {
177 25
            return self::BINN_STRING;
178
        }
179
180 29
        if (is_integer($value)) {
181 18
            return $this->detectInt($value);
182
        }
183
184 20
        if (is_float($value)) {
185 9
            if (strlen($value) > 4) {
186 9
                return self::BINN_FLOAT64;
187
            } else {
188 6
                return self::BINN_FLOAT32;
189
            }
190
        }
191
192 17
        if (is_array($value)) {
193 11
            foreach ($this->containersClasses as $contanerType => $containersClass) {
194 11
                if ($containersClass::validArray($value)) {
195 11
                    return $contanerType;
196
                }
197
            }
198
        }
199
200 6
        return self::BINN_NULL;
201
    }
202
203
    /**
204
     * Detect integer type
205
     *
206
     * @param $value
207
     * @return int
208
     */
209 21
    protected function detectInt($value)
210
    {
211 21
        if ($value < 0) {
212
            // int
213 18
            if ($value >= self::INT8_MIN) {
214 9
                return self::BINN_INT8;
215 18
            } else if ($value >= self::INT16_MIN) {
216 15
                return self::BINN_INT16;
217 9
            } else if ($value >= self::INT32_MIN) {
218 6
                return self::BINN_INT32;
219
            } else {
220 9
                return self::BINN_INT64;
221
            }
222
        } else {
223
            // uint
224 21
            if ($value <= self::UINT8_MAX) {
225 15
                return self::BINN_UINT8;
226 18
            } else if ($value <= self::UINT16_MAX) {
227 12
                return self::BINN_UINT16;
228 9
            } else if ($value <= self::UINT32_MAX) {
229 3
                return self::BINN_UINT32;
230
            } else {
231 9
                return self::BINN_UINT64;
232
            }
233
        }
234
    }
235
236
    /**
237
     * Get storage type
238
     *
239
     * @param $type
240
     * @return int
241
     */
242 77
    protected function storageType($type)
243
    {
244 77
        return $type & ($type ^ self::BINN_TYPE_MASK);
245
    }
246
247
    /**
248
     * Array associativity check
249
     * True if array is associative, False if array is sequential
250
     *
251
     * @param array $arr
252
     * @return bool
253
     */
254 42
    protected static function isArrayAssoc($arr)
255
    {
256 42
        $arr = (array)$arr;
257 42
        if (array() === $arr) return false;
258 42
        return array_keys($arr) !== range(0, count($arr) - 1);
259
    }
260
261
    /**
262
     * Array objectivity check
263
     * True if array is objective, False if array is sequential or have only number keys
264
     *
265
     * @param $arr
266
     * @return bool
267
     */
268 17
    protected static function isArrayObject($arr)
269
    {
270 17
        foreach(array_keys($arr) as $key) {
271 17
            if (!is_int($key)) {
272 17
                return true;
273
            }
274
        }
275
276 14
        return false;
277
    }
278
279
    /**
280
     * Calculate result binary Binn string size
281
     * @return int
282
     */
283 61
    protected function calculateSize()
284
    {
285 61
        $size = 0;
286
287 61
        if (($this->dataSize + $this->metaSize) > 127) {
288 12
            $size += 3;
289
        }
290
291 61
        if (count($this->binnArr) > 127) {
292 6
            $size += 3;
293
        }
294
295 61
        $this->size = ($this->dataSize + $this->metaSize) + $size;
296 61
        return $this->size;
297
    }
298
299
    /**
300
     *
301
     *  @return array
302
     */
303 49
    public function getBinnArr()
304
    {
305 49
        $return = [];
306
307 49
        foreach ($this->binnArr as $arr) {
308 49
            $storageType = $this->storageType($arr[self::KEY_TYPE]);
309
310 49
            if ($storageType === self::BINN_STORAGE_CONTAINER) {
311 14
                if (isset($arr[self::KEY_KEY])) {
312 11
                    $key = $arr[self::KEY_KEY];
313 11
                    $return[$key] = $arr[self::KEY_VAL]->getBinnArr();
314
                } else {
315 14
                    $return[] = $arr[self::KEY_VAL]->getBinnArr();
316
                }
317
            } else {
318 49
                if (isset($arr[self::KEY_KEY])) {
319 25
                    $key = $arr[self::KEY_KEY];
320 25
                    $return[$key] = $arr[self::KEY_VAL];
321
                } else {
322 49
                    $return[] = $arr[self::KEY_VAL];
323
                }
324
            }
325
        }
326
327 49
        return $return;
328
    }
329
330
    /**
331
     * Get binn size
332
     * @return int
333
     */
334 35
    public function binnSize()
335
    {
336 35
        return $this->calculateSize();
337
    }
338
339
    /**
340
     * Memory saving
341
     * If it possible:
342
     * Converting int64 to int32/int16/int8
343
     * Converting uint64 to uint32/uint16/uint8
344
     * Converting positive int to uint
345
     *
346
     * @param int   $type
347
     * @param mixed   $val
348
     *
349
     * @return int  $type2
350
     *
351
     */
352 42
    protected function compressInt($type, $val)
353
    {
354 42
        $newType = $type;
355
356 42
        if ($val >= 0) {
357
            // Convert to unsigned
358
            switch ($newType) {
359 39
                case self::BINN_INT64:
360 3
                    $newType = self::BINN_UINT64;
361 3
                    break;
362
363 39
                case self::BINN_INT32:
364 3
                    $newType = self::BINN_UINT32;
365 3
                    break;
366
367 39
                case self::BINN_INT16:
368 3
                    $newType = self::BINN_UINT16;
369 3
                    break;
370
371 39
                case self::BINN_INT8:
372 3
                    $newType = self::BINN_UINT8;
373 3
                    break;
374
            }
375
        }
376
377 42
        if (in_array($newType, [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16])) {
378
            // Signed
379 39
            if ($val >= self::INT8_MIN) {
380 3
                $newType = self::BINN_INT8;
381
            }
382 39
            elseif ($val >= self::INT16_MIN) {
383 36
                $newType = self::BINN_INT16;
384
            }
385 9
            elseif ($val >= self::INT32_MIN) {
386 6
                $newType = self::BINN_INT32;
387
            }
388
        }
389
390 42
        if (in_array($newType, [self::BINN_UINT64, self::BINN_UINT32, self::BINN_UINT16])) {
391
            // Unsigned
392
393 39
            if ($val <= self::UINT8_MAX) {
394 6
                $newType = self::BINN_UINT8;
395
            }
396 39
            elseif ($val <= self::UINT16_MAX) {
397 33
                $newType = self::BINN_UINT16;
398
            }
399 9
            elseif ($val <= self::UINT32_MAX) {
400 3
                $newType = self::BINN_UINT32;
401
            }
402
        }
403
404 42
        return $newType;
405
    }
406
407
    /**
408
     * Clear all binn data
409
     *
410
     * @return $this
411
     */
412 50
    public function binnFree()
413
    {
414
        // $this->binnType     = self::BINN_STORAGE_NOBYTES;
415
416 50
        $this->count        = 0;
417 50
        $this->dataSize     = 0;
418
419
        // Initial meta size 3 bytes
420
        // Type byte + Size byte + Item counts byte
421 50
        $this->metaSize    = self::MIN_BINN_SIZE;
422
423 50
        $this->size         = 0;
424 50
        $this->binnString   = "";
425
426 50
        $this->binnArr      = [];
427
428 50
        return $this;
429
    }
430
431
    /**
432
     * Unpack value
433
     *
434
     * @param $varType
435
     * @param $value
436
     * @return bool|null
437
     */
438 46
    protected function unpack($varType, $value)
439
    {
440 46
        if ($varType === self::BINN_TRUE) {
441 6
            return true;
442 46
        } else if ($varType === self::BINN_FALSE) {
443 6
            return false;
444 46
        } else if ($varType === self::BINN_UINT64) {
445 6
            return unpack("J", $value)[1];
446 46
        } else if ($varType === self::BINN_UINT32) {
447 12
            return unpack("N", $value)[1];
448 46
        } else if ($varType === self::BINN_UINT16) {
449 18
            return unpack("n", $value)[1];
450 46
        } else if ($varType == self::BINN_UINT8) {
451 46
            return unpack("C", $value)[1];
452 46
        } else if ($varType === self::BINN_INT8) {
453 9
            return unpack("c", $value)[1];
454 46
        } else if ($varType === self::BINN_INT16) {
455 18
            return unpack("s", strrev($value))[1];
456 46
        } else if ($varType === self::BINN_INT32) {
457 17
            return unpack("i", strrev($value))[1];
458 46
        } else if ($varType === self::BINN_INT64) {
459 6
            return unpack("q", strrev($value))[1];
460 43
        } else if ($varType === self::BINN_FLOAT32) {
461 6
            return unpack("f", strrev($value))[1];
462 43
        } else if ($varType === self::BINN_FLOAT64) {
463 9
            return unpack("d", strrev($value))[1];
464 37
        } else if ($varType === self::BINN_STRING) {
465 37
            return unpack("a*", $value)[1];
466
        }
467
        
468 3
        return null;
469
    }
470
471
    /**
472
     * Pack value
473
     *
474
     * @param $varType
475
     * @param mixed $value
476
     * @return null|string
477
     */
478 55
    protected function pack($varType, $value = null)
479
    {
480 55
        if ($varType === self::BINN_TRUE) {
481 3
            return pack("C", self::BINN_TRUE);
482 55
        } else if ($varType === self::BINN_FALSE) {
483 3
            return pack("C", self::BINN_FALSE);
484 55
        } else if ($varType === self::BINN_UINT64) {
485 6
            return pack("J", $value);
486 55
        } else if ($varType === self::BINN_UINT32) {
487 12
            return pack("N", $value);
488 55
        } else if ($varType === self::BINN_UINT16) {
489 18
            return pack("n", $value);
490 55
        } else if ($varType === self::BINN_UINT8) {
491 52
            return pack("C", $value);
492 55
        } else if ($varType === self::BINN_INT8) {
493 9
            return pack("c", $value);
494 55
        } else if ($varType === self::BINN_INT16) {
495 21
            return strrev(pack("s", $value));
496 49
        } else if ($varType === self::BINN_INT32) {
497 14
            return strrev(pack("i", $value));
498 49
        } else if ($varType === self::BINN_INT64) {
499 6
            return strrev(pack("q", $value));
500 46
        } else if ($varType === self::BINN_FLOAT32) {
501 6
            return strrev(pack("f", $value));
502 46
        } else if ($varType === self::BINN_FLOAT64) {
503 9
            return strrev(pack("d", $value));
504 43
        } else if ($varType === self::BINN_STRING) {
505 40
            return pack("a*", $value);
506 43
        } else if ($varType === self::BINN_NULL) {
507 40
            return pack("x");
508
        }
509
        
510 3
        return null;
511
    }
512
513
    /**
514
     * Pack varType
515
     *
516
     * @param $type
517
     * @return string
518
     */
519 52
    protected function packType($type)
520
    {
521 52
        return $this->pack(self::BINN_UINT8, $type);
522
    }
523
524
    /**
525
     * Pack size info
526
     *
527
     * @param $size
528
     * @return string
529
     */
530 52
    protected function packSize($size)
531
    {
532 52
        return ($size <= 127)
533 46
            ? $this->pack(self::BINN_UINT8, $size)
534 52
            : $this->getInt32Binsize($size);
535
    }
536
537
    /**
538
     * Get size info
539
     * data and meta (type info, size info, null bytes)
540
     *
541
     * @param $type
542
     * @param string $value
543
     * @return array
544
     */
545 74
    protected function getTypeSize($type, $value = '')
546
    {
547 74
        $size = ['meta' => 0, 'data' => 0];
548 74
        $storageType = $this->storageType($type);
549
550 74
        if ($type == self::BINN_BOOL
551 74
            || $type == self::BINN_TRUE
552 74
            || $type == self::BINN_FALSE
553
        ) {
554 8
            $size = ['meta' => 1, 'data' => 0];
555 74
        } else if ($storageType === self::BINN_STORAGE_CONTAINER) {
556 26
            $size = ['meta' => 0, 'data' => $value->binnSize()];
557 74
        } else if ($storageType === self::BINN_STORAGE_BLOB) {
558 2
            $dataSize = mb_strlen($value);
559
560 2
            $metaSize = $dataSize > 127 ? 4 : 1; // size byte
561 2
            $metaSize += 1; // type byte
562
563 2
            $size = ['meta' => $metaSize, 'data' => $dataSize];
564 74
        } else if ($storageType === self::BINN_STORAGE_STRING) {
565 59
            $dataSize = mb_strlen($value);
566
567 59
            $metaSize = $dataSize > 127 ? 4 : 1; // size byte
568 59
            $metaSize += 2; // type byte + null terminated
569
            
570 59
            $size = ['meta' => $metaSize, 'data' => $dataSize];
571 48
        } else if ($storageType === self::BINN_STORAGE_QWORD) {
572 12
            $size = ['meta' => 1, 'data' => 8];
573 45
        } else if ($storageType === self::BINN_STORAGE_DWORD) {
574 6
            $size = ['meta' => 1, 'data' => 4];
575 42
        } else if ($storageType === self::BINN_STORAGE_WORD) {
576 36
            $size = ['meta' => 1, 'data' => 2];
577 33
        } else if ($storageType === self::BINN_STORAGE_BYTE) {
578 33
            $size = ['meta' => 1, 'data' => 1];
579 3
        } else if ($storageType === self::BINN_STORAGE_NOBYTES) {
580 3
            $size = ['meta' => 1, 'data' => 0];
581
        }
582
        
583 74
        return $size;
584
    }
585
}