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

BinnMap   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Test Coverage

Coverage 84%

Importance

Changes 0
Metric Value
eloc 145
dl 0
loc 284
ccs 126
cts 150
cp 0.84
rs 8.8
c 0
b 0
f 0
wmc 45

8 Methods

Rating   Name   Duplication   Size   Complexity  
A unserialize() 0 10 2
A validArray() 0 12 3
A binnOpen() 0 4 2
C _binnLoad() 0 93 16
A __construct() 0 10 2
B getBinnVal() 0 47 11
A _addVal() 0 24 2
B serialize() 0 32 7

How to fix   Complexity   

Complex Class

Complex classes like BinnMap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BinnMap, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Knik\Binn;
4
5
use Knik\Binn\Exceptions\InvalidArrayException;
6
7
class BinnMap extends BinnAbstract
8
{
9
    protected $binnType = self::BINN_MAP;
10
11 9
    public function __construct($binnString = '')
12
    {
13 9
        $this->binnType = self::BINN_MAP;
14 9
        $this->binnClass = self::class;
15
16 9
        if ($binnString != '') {
17 3
            $this->_binnLoad($binnString);
18
        }
19
20 9
        return $this;
21
    }
22
23
    /**
24
     * @param string $binnString
25
     */
26 6
    public function binnOpen($binnString = '')
27
    {
28 6
        if ($binnString != '') {
29 6
            $this->_binnLoad($binnString);
30
        }
31 6
    }
32
33
    /**
34
     * @param integer $key
35
     * @param int   $type
36
     * @param mixed $value
37
     */
38 9
    private function _addVal($key, $type, $value)
39
    {
40 9
        if (in_array($type,
41 9
            [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16,
42 9
                self::BINN_UINT64,self::BINN_UINT32, self::BINN_UINT16])
43
        ) {
44 3
            $type = $this->compressInt($type, $value);
45
        }
46
47 9
        $size = $this->getTypeSize($type, $value);
48
49 9
        $this->dataSize += $size['data'];
50 9
        $this->metaSize += $size['meta'];
51
52
        // Key size. 4 bytes
53 9
        $this->metaSize += 4;
54
55 9
        $this->count++;
56
57 9
        $this->binnArr[] = [
58 9
            self::KEY_TYPE      => $type,
59 9
            self::KEY_VAL       => $value,
60 9
            self::KEY_SIZE      => $size['data'],
61 9
            self::KEY_KEY       => $key,
62
        ];
63 9
    }
64
65
    /**
66
     * @param string
67
     */
68 9
    private function _binnLoad($binnString)
69
    {
70 9
        $pos = 1; // Position
71 9
        $sizeBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
72
73
        // Size
74 9
        if ($sizeBytes & 1 << 7) {
75
            $sizeBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
76
            $this->size = ($sizeBytes &~ (1 << 31)); // Cut bit
77
            $pos += 4;
78
        } else {
79 9
            $this->size = $sizeBytes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sizeBytes can also be of type boolean. However, the property $size is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
80 9
            $pos += 1;
81
        }
82
83 9
        unset($sizeBytes);
84
85 9
        $countBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
86
87
        // Size
88 9
        if ($countBytes & 1 << 7) {
89
            $countBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
90
            $this->count = ($countBytes &~ (1 << 31)); // Cut bit
91
            $pos += 4;
92
        } else {
93 9
            $this->count = $countBytes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $countBytes can also be of type boolean. However, the property $count is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
94 9
            $pos += 1;
95
        }
96
97 9
        unset($countBytes);
98
99
        // Data
100 9
        $stopWhile = false;
101 9
        while ($pos < $this->size && !$stopWhile) {
102 9
            $varKey = $this->unpack(self::BINN_INT32, substr($binnString, $pos, 4));
103 9
            $pos += 4;
104
105 9
            $varType = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
106 9
            $varStorageType = $this->storageType($varType);
107 9
            $pos += 1;
108
109 9
            if ($varStorageType === self::BINN_STORAGE_QWORD
110 9
                || $varStorageType === self::BINN_STORAGE_DWORD
111 9
                || $varStorageType === self::BINN_STORAGE_WORD
112 9
                || $varStorageType === self::BINN_STORAGE_BYTE
113 9
                || $varStorageType === self::BINN_STORAGE_NOBYTES
114
            ) {
115 3
                $varSize = $this->getTypeSize($varType);
116 3
                $val = $this->unpack($varType, substr($binnString, $pos, $varSize['data']));
117 3
                $this->_addVal($varKey, $varType, $val);
0 ignored issues
show
Bug introduced by
It seems like $varType can also be of type boolean; however, parameter $type of Knik\Binn\BinnMap::_addVal() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

117
                $this->_addVal($varKey, /** @scrutinizer ignore-type */ $varType, $val);
Loading history...
Bug introduced by
It seems like $varKey can also be of type boolean; however, parameter $key of Knik\Binn\BinnMap::_addVal() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

117
                $this->_addVal(/** @scrutinizer ignore-type */ $varKey, $varType, $val);
Loading history...
118 3
                $pos += $varSize['data'];
119
120 9
            } else if ($varStorageType === self::BINN_STRING ) {
121 9
                $stringSize = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
122
123
                // Size
124 9
                if ($stringSize & 1 << 7) {
125
                    $stringSize = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
126
                    $stringSize = ($stringSize &~ (1 << 31)); // Cut bit
127
                    $pos += 4;
128
                } else {
129 9
                    $pos += 1;
130
                }
131
132 9
                $this->_addVal($varKey,self::BINN_STRING, $this->unpack(
133 9
                    self::BINN_STRING,
134 9
                    substr($binnString, $pos, $stringSize)
0 ignored issues
show
Bug introduced by
It seems like $stringSize can also be of type boolean; however, parameter $length of substr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
                    substr($binnString, $pos, /** @scrutinizer ignore-type */ $stringSize)
Loading history...
135
                ));
136
137 9
                $pos += $stringSize;
138 9
                $pos += 1; // Null byte
139 6
            } else if ($varStorageType === self::BINN_STORAGE_CONTAINER) {
140 6
                $list_size = $this->unpack(self::BINN_UINT8, $binnString[$pos]);;
141
142
                // Size
143 6
                if ($list_size & 1 << 7) {
144
                    $list_size = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
145
                    $list_size = ($list_size &~ (1 << 31)); // Cut bit
146
                }
147
148 6
                $substring = substr($binnString, $pos-1, $list_size);
149
150 6
                foreach ($this->containersClasses as $containerType => $containersClass) {
151 6
                    if ($containerType === $varType) {
152 6
                        $container = new $containersClass($substring);
153 6
                        $this->_addVal($varKey, $varType, $container);
154 6
                        break;
155
                    }
156
                }
157
158 6
                $pos += ($list_size-1);
159
            } else {
160
                $stopWhile = true;
161
            }
162
        }
163 9
    }
164
165
    /**
166
     * Get binary string
167
     *
168
     * @return string
169
     */
170 6
    public function getBinnVal()
171
    {
172 6
        $this->calculateSize();
173
174 6
        $this->binnString = '';
175 6
        $this->binnString .= $this->pack(self::BINN_UINT8, $this->binnType);
176
177 6
        $this->binnString .= $this->packSize($this->size);
178
179 6
        $count = count($this->binnArr);
180 6
        $this->binnString .= $this->packSize($count);
181
182 6
        foreach ($this->binnArr as &$arr) {
183 6
            $key = $arr[self::KEY_KEY];
184 6
            $type = $arr[self::KEY_TYPE];
185 6
            $storageType = $this->storageType($type);
186
187 6
            $this->binnString .= $this->pack(self::BINN_INT32, $key);
188
189 6
            if ($type === self::BINN_BOOL) {
190
                $this->binnString .= $arr[self::KEY_VAL]
191
                    ? $this->packType(self::BINN_TRUE)
192
                    : $this->packType(self::BINN_FALSE);
193
194
                continue;
195
            }
196
197 6
            if ($storageType === self::BINN_STORAGE_QWORD
198 6
                || $storageType === self::BINN_STORAGE_DWORD
199 6
                || $storageType === self::BINN_STORAGE_WORD
200 6
                || $storageType === self::BINN_STORAGE_BYTE
201
            ) {
202 3
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
203 3
                $this->binnString .= $this->pack($arr[self::KEY_TYPE], $arr[self::KEY_VAL]);
204 6
            } else if ($storageType === self::BINN_STORAGE_NOBYTES) {
205
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
206 6
            } else if ($storageType === self::BINN_STORAGE_STRING) {
207 6
                $this->binnString .= $this->packType(self::BINN_STRING);
208 6
                $this->binnString .= $this->packSize($arr[self::KEY_SIZE]);
209 6
                $this->binnString .= $this->pack(self::BINN_STRING, $arr[self::KEY_VAL]);
210 6
                $this->binnString .= $this->pack(self::BINN_NULL);
211 3
            } else if ($storageType === self::BINN_STORAGE_CONTAINER) {
212 6
                $this->binnString .= $arr[self::KEY_VAL]->getBinnVal();
213
            }
214
        }
215
216 6
        return $this->binnString;
217
    }
218
219
    /**
220
     * Check is valid array to serialize
221
     *
222
     * @param $array
223
     * @return bool
224
     */
225 3
    public static function validArray($array)
226
    {
227 3
        $array = (array)$array;
228 3
        if (!self::isArrayAssoc($array)) {
229 3
            return false;
230
        }
231
232 3
        if (self::isArrayObject($array)) {
233 3
            return false;
234
        }
235
236 3
        return true;
237
    }
238
239
    /**
240
     * @param array $array
241
     * @return string
242
     */
243 6
    public function serialize($array = [])
244
    {
245 6
        if (empty($array)) {
246 3
            return $this->getBinnVal();
247
        }
248
249 3
        $this->binnFree();
250
251 3
        if (! $this->isArrayAssoc($array)) {
252
            throw new InvalidArrayException('Array should be associative');
253
        }
254
255 3
        foreach ($array as $key => $item) {
256 3
            $type = $this->detectType($item);
257 3
            $storageType = $this->storageType($type);
258
259 3
            if ($storageType === self::BINN_STORAGE_CONTAINER) {
260
                foreach ($this->containersClasses as $contanerType => $containersClass)
261
                {
262
                    if ($containersClass::validArray($item)) {
263
                        $container = new $containersClass();
264
                        $container->serialize($item);
265
                        $item = $container;
266
                        break;
267
                    }
268
                }
269
            }
270
271 3
            $this->_addVal($key, $type, $item);
272
        }
273
274 3
        return $this->getBinnVal();
275
    }
276
277
    /**
278
     * @param string $binnString
279
     * @return array
280
     */
281 6
    public function unserialize($binnString = '')
282
    {
283 6
        if (empty($binnString)) {
284 3
            return $this->getBinnArr();
285
        }
286
287 3
        $this->binnFree();
288
289 3
        $this->binnOpen($binnString);
290 3
        return $this->getBinnArr();
291
    }
292
}