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

BinnObject   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Test Coverage

Coverage 86.67%

Importance

Changes 0
Metric Value
eloc 145
dl 0
loc 291
ccs 130
cts 150
cp 0.8667
rs 8.8798
c 0
b 0
f 0
wmc 44

8 Methods

Rating   Name   Duplication   Size   Complexity  
A validArray() 0 15 2
A binnOpen() 0 4 2
A unserialize() 0 10 2
B getBinnVal() 0 48 11
A _addVal() 0 24 2
B serialize() 0 32 7
C _binnLoad() 0 97 16
A __construct() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like BinnObject 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 BinnObject, 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 BinnObject extends BinnAbstract
8
{
9
    protected $binnType = self::BINN_OBJECT;
10
11 14
    public function __construct($binnString = '')
12
    {
13 14
        $this->binnClass = self::class;
14
15 14
        if ($binnString != '') {
16 6
            $this->_binnLoad($binnString);
17
        }
18
19 14
        return $this;
20
    }
21
22
    /**
23
     * @param string
24
     */
25 10
    private function _binnLoad($binnString)
26
    {
27 10
        $pos = 1; // Position
28 10
        $sizeBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
29
30
        // Size
31 10
        if ($sizeBytes & 1 << 7) {
32
            $sizeBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
33
            $this->size = ($sizeBytes &~ (1 << 31)); // Cut bit
34
            $pos += 4;
35
        } else {
36 10
            $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...
37 10
            $pos += 1;
38
        }
39
40 10
        unset($sizeBytes);
41
42 10
        $countBytes = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
43
44
        // Size
45 10
        if ($countBytes & 1 << 7) {
46
            $countBytes = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
47
            $this->count = ($countBytes &~ (1 << 31)); // Cut bit
48
            $pos += 4;
49
        } else {
50 10
            $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...
51 10
            $pos += 1;
52
        }
53
54 10
        unset($countBytes);
55
56
        // Data
57 10
        $stopWhile = false;
58 10
        while ($pos < $this->size && !$stopWhile) {
59
            // Key size
60 10
            $varKeySize = $this->unpack(self::BINN_UINT8, substr($binnString, $pos, 1));
61 10
            $pos += 1;
62
63 10
            $varKey = $this->unpack(self::BINN_STRING, substr($binnString, $pos, $varKeySize));
0 ignored issues
show
Bug introduced by
It seems like $varKeySize 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

63
            $varKey = $this->unpack(self::BINN_STRING, substr($binnString, $pos, /** @scrutinizer ignore-type */ $varKeySize));
Loading history...
64 10
            $pos += $varKeySize;
65
66 10
            $varType = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
67 10
            $varStorageType = $this->storageType($varType);
68 10
            $pos += 1;
69
70 10
            if ($varStorageType === self::BINN_STORAGE_QWORD
71 10
                || $varStorageType === self::BINN_STORAGE_DWORD
72 10
                || $varStorageType === self::BINN_STORAGE_WORD
73 10
                || $varStorageType === self::BINN_STORAGE_BYTE
74 10
                || $varStorageType === self::BINN_STORAGE_NOBYTES
75
            ) {
76 3
                $varSize = $this->getTypeSize($varType);
77 3
                $val = $this->unpack($varType, substr($binnString, $pos, $varSize['data']));
78 3
                $this->_addVal($varKey, $varType, $val);
0 ignored issues
show
Bug introduced by
It seems like $varKey can also be of type boolean; however, parameter $key of Knik\Binn\BinnObject::_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

78
                $this->_addVal(/** @scrutinizer ignore-type */ $varKey, $varType, $val);
Loading history...
Bug introduced by
It seems like $varType can also be of type boolean; however, parameter $type of Knik\Binn\BinnObject::_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

78
                $this->_addVal($varKey, /** @scrutinizer ignore-type */ $varType, $val);
Loading history...
79 3
                $pos += $varSize['data'];
80
81 10
            } else if ($varStorageType === self::BINN_STRING ) {
82 8
                $stringSize = $this->unpack(self::BINN_UINT8, $binnString[$pos]);
83
84
                // Size
85 8
                if ($stringSize & 1 << 7) {
86
                    $stringSize = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
87
                    $stringSize = ($stringSize &~ (1 << 31)); // Cut bit
88
                    $pos += 4;
89
                } else {
90 8
                    $pos += 1;
91
                }
92
93 8
                $this->_addVal($varKey,self::BINN_STRING, $this->unpack(
94 8
                    self::BINN_STRING,
95 8
                    substr($binnString, $pos, $stringSize)
96
                ));
97
98 8
                $pos += $stringSize;
99 8
                $pos += 1; // Null byte
100 2
            } else if ($varStorageType === self::BINN_STORAGE_CONTAINER) {
101 2
                $list_size = $this->unpack(self::BINN_UINT8, $binnString[$pos]);;
102
103
                // Size
104 2
                if ($list_size & 1 << 7) {
105
                    $list_size = $this->unpack(self::BINN_UINT32, substr($binnString, $pos, 4));
106
                    $list_size = ($list_size &~ (1 << 31)); // Cut bit
107
                }
108
109 2
                $substring = substr($binnString, $pos-1, $list_size);
110
111 2
                foreach ($this->containersClasses as $containerType => $containersClass) {
112 2
                    if ($containerType === $varType) {
113 2
                        $container = new $containersClass($substring);
114 2
                        $this->_addVal($varKey, $varType, $container);
115 2
                        break;
116
                    }
117
                }
118
119 2
                $pos += ($list_size-1);
120
            } else {
121
                $stopWhile = true;
122
            }
123
        }
124 10
    }
125
126
    /**
127
     * Get binary string
128
     *
129
     * @return string
130
     */
131 7
    public function getBinnVal()
132
    {
133 7
        $this->calculateSize();
134
135 7
        $this->binnString = '';
136 7
        $this->binnString .= $this->pack(self::BINN_UINT8, $this->binnType);
137
138 7
        $this->binnString .= $this->packSize($this->size);
139
140 7
        $count = count($this->binnArr);
141 7
        $this->binnString .= $this->packSize($count);
142
143 7
        foreach ($this->binnArr as &$arr) {
144 7
            $key = $arr[self::KEY_KEY];
145 7
            $type = $arr[self::KEY_TYPE];
146 7
            $storageType = $this->storageType($type);
147
148 7
            $this->binnString .= $this->pack(self::BINN_UINT8, mb_strlen($key));
149 7
            $this->binnString .= $this->pack(self::BINN_STRING, $key);
150
151 7
            if ($type === self::BINN_BOOL) {
152
                $this->binnString .= $arr[self::KEY_VAL]
153
                    ? $this->packType(self::BINN_TRUE)
154
                    : $this->packType(self::BINN_FALSE);
155
156
                continue;
157
            }
158
159 7
            if ($storageType === self::BINN_STORAGE_QWORD
160 7
                || $storageType === self::BINN_STORAGE_DWORD
161 7
                || $storageType === self::BINN_STORAGE_WORD
162 7
                || $storageType === self::BINN_STORAGE_BYTE
163
            ) {
164
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
165
                $this->binnString .= $this->pack($arr[self::KEY_TYPE], $arr[self::KEY_VAL]);
166 7
            } else if ($storageType === self::BINN_STORAGE_NOBYTES) {
167
                $this->binnString .= $this->packType($arr[self::KEY_TYPE]);
168 7
            } else if ($storageType === self::BINN_STORAGE_STRING) {
169 5
                $this->binnString .= $this->packType(self::BINN_STRING);
170 5
                $this->binnString .= $this->packSize($arr[self::KEY_SIZE]);
171 5
                $this->binnString .= $this->pack(self::BINN_STRING, $arr[self::KEY_VAL]);
172 5
                $this->binnString .= $this->pack(self::BINN_NULL);
173 2
            } else if ($storageType === self::BINN_STORAGE_CONTAINER) {
174 7
                $this->binnString .= $arr[self::KEY_VAL]->getBinnVal();
175
            }
176
        }
177
178 7
        return $this->binnString;
179
    }
180
181
    /**
182
     * @param string $binnString
183
     */
184 4
    public function binnOpen($binnString = '')
185
    {
186 4
        if ($binnString != '') {
187 4
            $this->_binnLoad($binnString);
188
        }
189 4
    }
190
191
    /**
192
     * @param integer $key
193
     * @param int   $type
194
     * @param mixed $value
195
     */
196 12
    private function _addVal($key, $type, $value)
197
    {
198 12
        if (in_array($type,
199 12
            [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16,
200 12
                self::BINN_UINT64,self::BINN_UINT32, self::BINN_UINT16])
201
        ) {
202
            $type = $this->compressInt($type, $value);
203
        }
204
205 12
        $size = $this->getTypeSize($type, $value);
206
207 12
        $this->dataSize += $size['data'];
208 12
        $this->metaSize += $size['meta'];
209
210
        // Key size. Size size + strlen
211 12
        $this->metaSize += 1 + strlen($key);
212
213 12
        $this->count++;
214
215 12
        $this->binnArr[] = [
216 12
            self::KEY_TYPE      => $type,
217 12
            self::KEY_VAL       => $value,
218 12
            self::KEY_SIZE      => $size['data'],
219 12
            self::KEY_KEY       => $key,
220
        ];
221 12
    }
222
223
    /**
224
     * Check is valid array to serialize
225
     *
226
     * @param $array
227
     * @return bool
228
     */
229 3
    public static function validArray($array)
230
    {
231 3
        $array = (array)$array;
232
233
        /*
234
        if (count(array_filter(array_keys($array), 'is_string')) > 0) {
235
            return true;
236
        }
237
        */
238
239 3
        if (self::isArrayObject($array)) {
240 3
            return true;
241
        }
242
243 3
        return false;
244
    }
245
246
    /**
247
     * @param array $array
248
     * @return string
249
     */
250 9
    public function serialize($array = [])
251
    {
252 9
        if (empty($array)) {
253 3
            return $this->getBinnVal();
254
        }
255
256 6
        $this->binnFree();
257
258 6
        if (! $this->isArrayAssoc($array)) {
259 2
            throw new InvalidArrayException('Array should be associative');
260
        }
261
262 4
        foreach ($array as $key => $item) {
263 4
            $type = $this->detectType($item);
264 4
            $storageType = $this->storageType($type);
265
266 4
            if ($storageType === self::BINN_STORAGE_CONTAINER) {
267 2
                foreach ($this->containersClasses as $contanerType => $containersClass)
268
                {
269 2
                    if ($containersClass::validArray($item)) {
270 2
                        $container = new $containersClass();
271 2
                        $container->serialize($item);
272 2
                        $item = $container;
273 2
                        break;
274
                    }
275
                }
276
            }
277
278 4
            $this->_addVal($key, $type, $item);
279
        }
280
281 4
        return $this->getBinnVal();
282
    }
283
284
    /**
285
     * @param string $binnString
286
     * @return array
287
     */
288 7
    public function unserialize($binnString = '')
289
    {
290 7
        if (empty($binnString)) {
291 5
            return $this->getBinnArr();
292
        }
293
294 2
        $this->binnFree();
295
296 2
        $this->binnOpen($binnString);
297 2
        return $this->getBinnArr();
298
    }
299
}