Completed
Pull Request — master (#114)
by Charlotte
02:17
created

BitManipulation   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 0
dl 0
loc 284
ccs 0
cts 159
cp 0
rs 8.3999
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B nthBit() 0 18 5
C nthByte() 0 43 8
B partOfByte() 0 16 5
A hexArrayToString() 0 13 3
C bytesFromTo() 0 47 11
A bytesFromToString() 0 8 2
A intToBinaryString() 0 18 4
B binaryToInt() 0 23 5
A binaryToHex() 0 4 1
A frameSize() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like BitManipulation 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 BitManipulation, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is a part of Woketo package.
5
 *
6
 * (c) Nekland <[email protected]>
7
 *
8
 * For the full license, take a look to the LICENSE file
9
 * on the root directory of this project
10
 */
11
declare(strict_types=1);
12
13
namespace Nekland\Woketo\Utils;
14
15
/**
16
 * Class BitManipulation
17
 *
18
 * Glossary:
19
 *   - in this context a "frame" is an assembly of bytes represented by a "byte-string" or a (signed) int.
20
 */
21
class BitManipulation
22
{
23
    /**
24
     * Mode from to is the default mode of inspection of frames. But PHP usually uses from and length to inspect frames.
25
     */
26
    const MODE_FROM_TO = 0;
27
    const MODE_PHP = 1;
28
29
    /**
30
     * Get a specific bit from a byte.
31
     *
32
     * @param int $byte
33
     * @param int $bitNumber
34
     * @return int
35
     */
36
    public static function nthBit(int $byte, int $bitNumber) : int
37
    {
38
        if ($byte < 0 || $byte > 255) {
39
            throw new \InvalidArgumentException(
40
                \sprintf('The given integer %s is not a byte.', $byte)
41
            );
42
        }
43
44
        if ($bitNumber < 1 || $bitNumber > 8) {
45
            throw new \InvalidArgumentException(
46
                \sprintf('The bit number %s is not a correct value for a byte (1-8 required).', $bitNumber)
47
            );
48
        }
49
50
        $realNth = \pow(2, 8 - $bitNumber);
51
52
        return (int) ($realNth === ($byte & $realNth));
53
    }
54
55
    /**
56
     * Get a specific byte inside a frame represented by an int or a string.
57
     *
58
     * @param string|int $frame      Non utf8 string (this should be more precisely a bytes-string).
59
     * @param int        $byteNumber Starting at 0.
60
     * @return int
61
     */
62
    public static function nthByte($frame, int $byteNumber) : int
63
    {
64
        if (\is_string($frame)) {
65
            $len = BitManipulation::frameSize($frame);
66
67
            if ($byteNumber < 0 || $byteNumber > ($len-1)) {
68
                throw new \InvalidArgumentException(
69
                    \sprintf('The frame is only %s bytes larges but you tried to get the %sth byte.', $len, $byteNumber)
70
                );
71
            }
72
73
            return \ord($frame[$byteNumber]);
74
        }
75
76
        if (\is_int($frame)) {
77
            if ($frame < 0) {
78
                throw new \InvalidArgumentException(
79
                    \sprintf('This method does not support negative ints as parameter for now. %s given.', $byteNumber)
80
                );
81
            }
82
            $hex = \dechex($frame);
83
            $len = BitManipulation::frameSize($hex);
84
85
            // Index of the first octal of the wanted byte
86
            $realByteNth = $byteNumber * 2;
87
88
            if ($byteNumber < 0 || ($realByteNth + 1) > $len) {
89
                throw new \InvalidArgumentException(
90
                    \sprintf('Impossible to get the byte %s from the frame %s.', $byteNumber, $frame)
91
                );
92
            }
93
94
            // Considering FF12AB (number) if you want the byte represented by AB you need to get the
95
            // first letter, shift it of 4 and add the next letter.
96
            // This may seems weird but that's because you read numbers from right to left.
97
            return (\hexdec($hex[$realByteNth]) << 4) + \hexdec($hex[$realByteNth + 1]);
98
            // _Notice that if the number is from right to left, your data is still from left to right_
99
        }
100
101
        throw new \InvalidArgumentException(
102
            \sprintf('The frame must be an int or string, %s given.', gettype($frame))
103
        );
104
    }
105
106
    public static function partOfByte(int $byte, int $part) : int
107
    {
108
        if ($byte < 0 || $byte > 255) {
109
            throw new \InvalidArgumentException(sprintf('%s is not a byte', $byte));
110
        }
111
112
        if ($part === 1) {
113
            return ($byte & 240) >> 4;
114
        }
115
116
        if ($part === 2) {
117
            return $byte & 15;
118
        }
119
120
        throw new \InvalidArgumentException(sprintf('A byte have only 2 parts. %s asked.', $part));
121
    }
122
123
    /**
124
     * Because strings are the best way to store many bytes in PHP it can
125
     * be useful to make the conversion between hex (which are strings)
126
     * array to string.
127
     *
128
     * @param array $hexArray
129
     * @return string
130
     */
131
    public static function hexArrayToString(...$hexArray) : string
132
    {
133
        if (\is_array($hexArray[0])) {
134
            $hexArray = $hexArray[0];
135
        }
136
        
137
        $res = '';
138
        foreach ($hexArray as $hexNum) {
139
            $res .= \chr(\hexdec($hexNum));
140
        }
141
142
        return $res;
143
    }
144
145
    /**
146
     * @param string|int $frame
147
     * @param int        $from        Byte where to start (should be inferior to $to).
148
     * @param int        $to          Byte where to stop (considering it starts at 0). The `to` value include the target
149
     *                                byte.
150
     * @param bool       $force8bytes By default PHP have a wrong behavior with 8 bytes variables. If you have 8 bytes
151
     *                                the returned int will be negative (because unsigned integers does not exists in PHP)
152
     * @return int
153
     */
154
    public static function bytesFromTo($frame, int $from, int $to, bool $force8bytes = false) : int
155
    {
156
        // No more than 64b (which return negative number when the first bit is specified)
157
        if (($to - $from) > 7 && (!$force8bytes && ($to - $from) !== 8)) {
158
            if ($force8bytes) {
159
                throw new \InvalidArgumentException(sprintf('Not more than 8 bytes (64bit) is supported by this method and you asked for %s bytes.', $to - $from));
160
            }
161
            throw new \InvalidArgumentException('PHP limitation: getting more than 7 bytes will return a negative number because unsigned int does not exist.');
162
        }
163
164
        if (\is_string($frame)) {
165
            if ((BitManipulation::frameSize($frame) - 1) < $to) {
166
                throw new \InvalidArgumentException('The frame is not long enough.');
167
            }
168
169
            $subStringLength = $to - $from + 1;
170
            // Getting responsible bytes
171
            $subString = \substr($frame, $from, $subStringLength);
172
            $res = 0;
173
174
            // for each byte, getting ord
175
            for($i = 0; $i < $subStringLength; $i++) {
176
                $res <<= 8;
177
                $res += \ord($subString[$i]);
178
            }
179
180
            return $res;
181
        }
182
183
        if (!\is_int($frame)) {
184
            throw new \InvalidArgumentException(
185
                \sprintf('A frame can only be a string or int. %s given', gettype($frame))
186
            );
187
        }
188
189
        if ($frame < 0) {
190
            throw new \InvalidArgumentException('The frame cannot be a negative number');
191
        }
192
193
        $res = 0;
194
        for ($i = $from; $i <= $to; $i++) {
195
            $res <<= 8;
196
            $res += BitManipulation::nthByte($frame, $i);
197
        }
198
199
        return $res;
200
    }
201
202
    /**
203
     * Proxy to the substr to be sure to be use the right method (mb_substr)
204
     *
205
     * @param string $frame
206
     * @param int    $from
207
     * @param int    $to
208
     * @return string
209
     */
210
    public static function bytesFromToString(string $frame, int $from, int $to, int $mode = BitManipulation::MODE_FROM_TO) : string
211
    {
212
        if ($mode === BitManipulation::MODE_FROM_TO) {
213
            return \mb_substr($frame, $from, $to - $from + 1, '8bit');
214
        }
215
216
        return \mb_substr($frame, $from, $to, '8bit');
217
    }
218
219
    /**
220
     * Take a frame represented by a decimal int to transform it in a string.
221
     * Notice that any int is a frame and cannot be more than 8 bytes
222
     *
223
     * @param int      $frame
224
     * @param int|null $size  In bytes. This value should always be precise. Be careful if you don't !
225
     * @return string
226
     */
227
    public static function intToBinaryString(int $frame, int $size = 0) : string
228
    {
229
        $format = '*';
230
231
        switch(true) {
232
            case $size <= 2:
233
                $format = 'n*';
234
                break;
235
            case $size <= 4:
236
                $format = 'N*';
237
                break;
238
            case $size > 4:
239
                $format = 'J*';
240
                break;
241
        }
242
243
        return pack($format, $frame);
244
    }
245
246
    /**
247
     * Take an string frame and transform it to a decimal frame (inside an int).
248
     *
249
     * @param string $frame
250
     * @return int
251
     */
252
    public static function binaryToInt(string $frame) : int
253
    {
254
        $len = BitManipulation::frameSize($frame);
255
256
        if ($len > 8) {
257
            throw new \InvalidArgumentException(
258
                \sprintf('The string %s cannot be converted to int because an int cannot be more than 8 bytes (64b).', $frame)
259
            );
260
        }
261
262
        $format = '*';
263
264
        switch(true) {
265
            case $len <= 2:
266
                $format = 'n';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
267
            case $len <= 4:
268
                $format = 'N';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
269
            case $len > 4:
270
                $format = 'J';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
271
        }
272
273
        return unpack($format, $frame)[1];
274
    }
275
276
    /**
277
     * Method that return frame as hex (more readable).
278
     * Helpful for debug !
279
     *
280
     * @param string $frame
281
     * @return string
282
     */
283
    public static function binaryToHex(string $frame) : string
284
    {
285
        return unpack('H*', $frame)[1];
286
    }
287
288
    /**
289
     * Haters gonna hate. `strlen` cannot be trusted because of an option of mbstring extension, more info:
290
     * http://php.net/manual/fr/mbstring.overload.php
291
     * http://php.net/manual/fr/function.mb-strlen.php#77040
292
     *
293
     * @param string $frame
294
     * @return int
295
     */
296
    public static function frameSize(string $frame) : int
297
    {
298
        if (\extension_loaded('mbstring')) {
299
            return \mb_strlen($frame, '8bit');
300
        }
301
302
        return \strlen($frame);
303
    }
304
}
305