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

BinnList   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Test Coverage

Coverage 88.28%

Importance

Changes 0
Metric Value
eloc 156
dl 0
loc 304
ccs 128
cts 145
cp 0.8828
rs 8.72
c 0
b 0
f 0
wmc 46

9 Methods

Rating   Name   Duplication   Size   Complexity  
A binnOpen() 0 4 2
A validArray() 0 8 2
A __call() 0 8 2
A unserialize() 0 10 2
C _binnLoad() 0 90 16
B getBinnVal() 0 44 11
A _addVal() 0 20 2
B serialize() 0 32 7
A __construct() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like BinnList 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 BinnList, 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
/**
8
 * @method BinnList addBool(boolean $value)
9
 * @method BinnList addUint8(integer $value)
10
 * @method BinnList addUint16(integer $value)
11
 * @method BinnList addUint32(integer $value)
12
 * @method BinnList addUint64(integer $value)
13
 * @method BinnList addInt8(integer $value)
14
 * @method BinnList addInt16(integer $value)
15
 * @method BinnList addInt32(integer $value)
16
 * @method BinnList addInt64(integer $value)
17
 * @method BinnList addFloat(string $value)
18
 * @method BinnList addDouble(string $value)
19
 * @method BinnList addStr(string $value)
20
 * @method BinnList addList(Binn $value)
21
 * @method BinnList addMap(Binn $value)
22
 * @method BinnList addObject(Binn $value)
23
 *
24
 */
25
class BinnList extends BinnAbstract
26
{
27
    protected $binnType = self::BINN_LIST;
28
29
    /**
30
     * @var array
31
     */
32
    private $methodsAssignments = [
33
        'addBool'      => self::BINN_BOOL,
34
        'addUint8'     => self::BINN_UINT8,
35
        'addUint16'    => self::BINN_UINT16,
36
        'addUint32'    => self::BINN_UINT32,
37
        'addUint64'    => self::BINN_UINT64,
38
        'addInt8'      => self::BINN_INT8,
39
        'addInt16'     => self::BINN_INT16,
40
        'addInt32'     => self::BINN_INT32,
41
        'addInt64'     => self::BINN_INT64,
42
        'addFloat'     => self::BINN_FLOAT32,
43
        'addDouble'    => self::BINN_FLOAT64,
44
        'addStr'       => self::BINN_STRING,
45
        'addList'      => self::BINN_LIST,
46
        'addMap'       => self::BINN_MAP,
47
        'addObject'    => self::BINN_OBJECT,
48
    ];
49
50 42
    public function __construct($binnString = '')
51
    {
52 42
        $this->binnClass = self::class;
53
54 42
        if ($binnString != '') {
55 6
            $this->_binnLoad($binnString);
56
        }
57
58 42
        return $this;
59
    }
60
61
    /**
62
     * @param $name
63
     * @param $arguments
64
     * @return $this
65
     *
66
     * @throws \Exception
67
     */
68 27
    public function __call($name, $arguments)
69
    {
70 27
        if (array_key_exists($name, $this->methodsAssignments)) {
71 24
            $this->_addVal($this->methodsAssignments[$name], $arguments[0]);
72 24
            return $this;
73
        }
74
75 3
        throw new \Exception("Call to undefined method {$name}");
76
    }
77
78
    /**
79
     * @param string $binnString
80
     */
81 12
    public function binnOpen($binnString = '')
82
    {
83 12
        if ($binnString != '') {
84 12
            $this->_binnLoad($binnString);
85
        }
86 12
    }
87
88
    /**
89
     * Get binary string
90
     *
91
     * @return string
92
     */
93 27
    public function getBinnVal()
94
    {
95 27
        $this->calculateSize();
96
97 27
        $this->binnString = '';
98 27
        $this->binnString .= $this->pack(self::BINN_UINT8, $this->binnType);
99
100 27
        $this->binnString .= $this->packSize($this->size);
101
102 27
        $count = count($this->binnArr);
103 27
        $this->binnString .= $this->packSize($count);
104
105 27
        foreach ($this->binnArr as &$arr) {
106 27
            $type = $arr[self::KEY_TYPE];
107 27
            $storageType = $this->storageType($type);
108
109 27
            if ($type === self::BINN_BOOL) {
110 3
                $this->binnString .= $arr[self::KEY_VAL]
111 3
                    ? $this->packType(self::BINN_TRUE)
112 3
                    : $this->packType(self::BINN_FALSE);
113
114 3
                continue;
115
            }
116
117 27
            if ($storageType === self::BINN_STORAGE_QWORD
118 24
                || $storageType === self::BINN_STORAGE_DWORD
119 21
                || $storageType === self::BINN_STORAGE_WORD
120 27
                || $storageType === self::BINN_STORAGE_BYTE
121
            ) {
122 18
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
123 18
                $this->binnString .= $this->pack($arr[self::KEY_TYPE], $arr[self::KEY_VAL]);
124 15
            } else if ($storageType === self::BINN_STORAGE_NOBYTES) {
125
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
126 15
            } else if ($storageType === self::BINN_STORAGE_STRING) {
127 15
                $this->binnString .= $this->packType(self::BINN_STRING);
128 15
                $this->binnString .= $this->packSize($arr[self::KEY_SIZE]);
129 15
                $this->binnString .= $this->pack(self::BINN_STRING, $arr[self::KEY_VAL]);
130 15
                $this->binnString .= $this->pack(self::BINN_NULL);
131 6
            } else if ($storageType === self::BINN_STORAGE_CONTAINER) {
132 27
                $this->binnString .= $arr[self::KEY_VAL]->getBinnVal();
133
            }
134
        }
135
136 27
        return $this->binnString;
137
    }
138
139
    /**
140
     * Check is valid array to serialize
141
     *
142
     * @param $array
143
     * @return bool
144
     */
145 6
    public static function validArray($array)
146
    {
147 6
        $array = (array)$array;
148 6
        if (self::isArrayAssoc($array)) {
149 3
            return false;
150
        }
151
152 6
        return true;
153
    }
154
155
    /**
156
     * @param array $array
157
     * @return string
158
     */
159 12
    public function serialize($array = [])
160
    {
161 12
        if (empty($array)) {
162 3
            return $this->getBinnVal();
163
        }
164
165 12
        $this->binnFree();
166
167 12
        if ($this->isArrayAssoc($array)) {
168 3
            throw new InvalidArrayException('Array should be sequential');
169
        }
170
171 9
        foreach ($array as $item) {
172 9
            $type = $this->detectType($item);
173 9
            $storageType = $this->storageType($type);
174
175 9
            if ($storageType === self::BINN_STORAGE_CONTAINER) {
176 3
                foreach ($this->containersClasses as $contanerType => $containersClass)
177
                {
178 3
                    if ($containersClass::validArray($item)) {
179 3
                        $container = new $containersClass();
180 3
                        $container->serialize($item);
181 3
                        $item = $container;
182 3
                        break;
183
                    }
184
                }
185
            }
186
187 9
            $this->_addVal($type, $item);
188
        }
189
190 9
        return $this->getBinnVal();
191
    }
192
193
    /**
194
     * @param string $binnString
195
     * @return array
196
     */
197 9
    public function unserialize($binnString = '')
198
    {
199 9
        if (empty($binnString)) {
200 6
            return $this->getBinnArr();
201
        }
202
203 6
        $this->binnFree();
204
205 6
        $this->binnOpen($binnString);
206 6
        return $this->getBinnArr();
207
    }
208
209
    /**
210
     * @param int   $type
211
     * @param mixed $value
212
     */
213 36
    private function _addVal($type, $value)
214
    {
215 36
        if (in_array($type,
216 36
            [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16,
217 36
                self::BINN_UINT64,self::BINN_UINT32, self::BINN_UINT16])
218
        ) {
219 18
            $type = $this->compressInt($type, $value);
220
        }
221
222 36
        $size = $this->getTypeSize($type, $value);
223
224 36
        $this->dataSize += $size['data'];
225 36
        $this->metaSize += $size['meta'];
226
227 36
        $this->count++;
228
229 36
        $this->binnArr[] = [
230 36
            self::KEY_TYPE      => $type,
231 36
            self::KEY_VAL       => $value,
232 36
            self::KEY_SIZE      => $size['data']
233
        ];
234 36
    }
235
236
    /**
237
     * @param string
238
     */
239 18
    private function _binnLoad($binnString)
240
    {
241 18
        $pos = 1; // Position
242 18
        $sizeBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
243
244
        // Size
245 18
        if ($sizeBytes & 1 << 7) {
246 6
            $sizeBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
247 6
            $this->size = ($sizeBytes &~ (1 << 31)); // Cut bit
248 6
            $pos += 4;
249
        } else {
250 12
            $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...
251 12
            $pos += 1;
252
        }
253
254 18
        unset($sizeBytes);
255
256 18
        $countBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
257
258
        // Size
259 18
        if ($countBytes & 1 << 7) {
260 3
            $countBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
261 3
            $this->count = ($countBytes &~ (1 << 31)); // Cut bit
262 3
            $pos += 4;
263
        } else {
264 15
            $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...
265 15
            $pos += 1;
266
        }
267
268 18
        unset($countBytes);
269
270
        // Data
271 18
        $stop_while = false;
272 18
        while ($pos < $this->size && !$stop_while) {
273 18
            $varType = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
274 18
            $varStorageType = $this->storageType($varType);
275 18
            $pos += 1;
276
277 18
            if ($varStorageType === self::BINN_STORAGE_QWORD
278 15
                || $varStorageType === self::BINN_STORAGE_DWORD
279 12
                || $varStorageType === self::BINN_STORAGE_WORD
280 9
                || $varStorageType === self::BINN_STORAGE_BYTE
281 18
                || $varStorageType === self::BINN_STORAGE_NOBYTES
282
            ) {
283 15
                $varSize = $this->getTypeSize($varType);
284 15
                $val = $this->unpack($varType, substr($binnString, $pos, $varSize['data']));
285 15
                $this->_addVal($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\BinnList::_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

285
                $this->_addVal(/** @scrutinizer ignore-type */ $varType, $val);
Loading history...
286 15
                $pos += $varSize['data'];
287
288 9
            } else if ($varStorageType === self::BINN_STRING ) {
289 9
                $stringSize = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
290
291
                // Size
292 9
                if ($stringSize & 1 << 7) {
293
                    $stringSize = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
294
                    $stringSize = ($stringSize &~ (1 << 31)); // Cut bit
295
                    $pos += 4;
296
                } else {
297 9
                    $pos += 1;
298
                }
299
300 9
                $this->_addVal(self::BINN_STRING, $this->unpack(
301 9
                    self::BINN_STRING,
302 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

302
                    substr($binnString, $pos, /** @scrutinizer ignore-type */ $stringSize)
Loading history...
303
                ));
304
305 9
                $pos += $stringSize;
306 9
                $pos += 1; // Null byte
307
            } else if ($varStorageType === self::BINN_STORAGE_CONTAINER) {
308
                $list_size = $this->unpack(self::BINN_UINT8, $binnString[$pos]);;
309
310
                // Size
311
                if ($list_size & 1 << 7) {
312
                    $list_size = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
313
                    $list_size = ($list_size &~ (1 << 31)); // Cut bit
314
                }
315
316
                $substring = substr($binnString, $pos-1, $list_size);
317
318
                foreach ($this->containersClasses as $containerType => $containersClass) {
319
                    if ($containerType === $varType) {
320
                        $container = new $containersClass($substring);
321
                        $this->_addVal($varType, $container);
322
                        break;
323
                    }
324
                }
325
326
                $pos += ($list_size-1);
327
            } else {
328
                $stop_while = true;
329
            }
330
        }
331
    }
332
}