Passed
Branch master (5a719b)
by Marc
03:59
created

EnumSet   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 818
Duplicated Lines 8.31 %

Test Coverage

Coverage 100%

Importance

Changes 15
Bugs 6 Features 2
Metric Value
dl 68
loc 818
ccs 286
cts 286
cp 1
rs 1.0434
c 15
b 6
f 2
wmc 101

44 Methods

Rating   Name   Duplication   Size   Complexity  
A symDiff() 12 12 2
A isSubset() 7 7 2
B doSetBinaryBitsetLeInt() 0 19 5
F doCountInt() 0 34 13
A getBit() 0 7 3
A doUnsetBitInt() 0 3 1
B doGetOrdinalsBin() 0 19 5
A getValues() 0 8 2
A setBit() 0 10 4
A doRewindBin() 0 7 2
A setBinaryBitsetLe() 0 10 2
A getOrdinals() 0 3 1
B doCountBin() 0 19 5
A doGetBitInt() 0 3 1
A doGetBinaryBitsetLeInt() 0 4 2
A doSetBitInt() 0 3 1
A doGetBinaryBitsetLeBin() 0 3 1
A getEnumeration() 0 3 1
A doSetBitBin() 0 4 1
A intersect() 12 12 2
A doRewindInt() 0 7 2
A getEnumerators() 0 8 2
A isEqual() 0 4 2
A getNames() 0 8 2
A getBinaryBitsetLe() 0 3 1
A union() 12 12 2
A setBinaryBitsetBe() 0 6 2
A diff() 12 12 2
A doGetBitBin() 0 3 1
A doUnsetBitBin() 0 4 1
A getBinaryBitsetBe() 0 3 1
B doSetBinaryBitsetLeBin() 0 28 6
A doGetOrdinalsInt() 0 11 3
A isSuperset() 7 7 2
B __construct() 0 29 3
A valid() 0 3 2
A key() 0 3 1
A rewind() 0 3 1
A contains() 0 4 1
A detach() 0 4 1
A next() 0 6 3
A attach() 0 4 1
A count() 0 3 1
A current() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EnumSet 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 EnumSet, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace MabeEnum;
4
5
use Countable;
6
use Iterator;
7
use InvalidArgumentException;
8
9
/**
10
 * A set of enumerators of the given enumeration (EnumSet<T>)
11
 * based on an integer or binary bitset depending of given enumeration size.
12
 *
13
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
14
 * @copyright Copyright (c) 2017 Marc Bennewitz
15
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
16
 */
17
class EnumSet implements Iterator, Countable
18
{
19
    /**
20
     * The classname of the Enumeration
21
     * @var string
22
     */
23
    private $enumeration;
24
25
    /**
26
     * Ordinal number of current iterator position
27
     * @var int
28
     */
29
    private $ordinal = 0;
30
31
    /**
32
     * Highest possible ordinal number
33
     * @var int
34
     */
35
    private $ordinalMax;
36
37
    /**
38
     * Integer or binary (little endian) bitset
39
     * @var int|string
40
     */
41
    private $bitset = 0;
42
43
    /**#@+
44
     * Defines private method names to be called depended of how the bitset type was set too.
45
     * ... Integer or binary bitset.
46
     * ... *Int or *Bin method
47
     * 
48
     * @var string
49
     */
50
    private $fnDoRewind            = 'doRewindInt';
51
    private $fnDoCount             = 'doCountInt';
52
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';
53
    private $fnDoGetBit            = 'doGetBitInt';
54
    private $fnDoSetBit            = 'doSetBitInt';
55
    private $fnDoUnsetBit          = 'doUnsetBitInt';
56
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
57
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
58
    /**#@-*/
59
60
    /**
61
     * Constructor
62
     *
63
     * @param string $enumeration The classname of the enumeration
64
     * @throws InvalidArgumentException
65
     */
66 195
    public function __construct($enumeration)
67
    {
68 195
        if (!\is_subclass_of($enumeration, Enum::class)) {
69 3
            throw new InvalidArgumentException(\sprintf(
70 3
                "%s can handle subclasses of '%s' only",
71 3
                static::class,
72 2
                Enum::class
73 1
            ));
74
        }
75
76 192
        $this->enumeration = $enumeration;
77 192
        $this->ordinalMax  = count($enumeration::getConstants());
78
79
        // By default the bitset is initialized as integer bitset
80
        // in case the enumeraton has more enumerators then integer bits
81
        // we will switch this into a binary bitset
82 192
        if ($this->ordinalMax > \PHP_INT_SIZE * 8) {
83
            // init binary bitset with zeros
84 48
            $this->bitset = \str_repeat("\0", (int)\ceil($this->ordinalMax / 8));
85
86
            // switch internal binary bitset functions
87 48
            $this->fnDoRewind            = 'doRewindBin';
88 48
            $this->fnDoCount             = 'doCountBin';
89 48
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
90 48
            $this->fnDoGetBit            = 'doGetBitBin';
91 48
            $this->fnDoSetBit            = 'doSetBitBin';
92 48
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
93 48
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
94 48
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
95 16
        }
96 192
    }
97
98
    /**
99
     * Get the classname of the enumeration
100
     * @return string
101
     */
102 3
    public function getEnumeration()
103
    {
104 3
        return $this->enumeration;
105
    }
106
107
    /**
108
     * Attach a new enumerator or overwrite an existing one
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return void
111
     * @throws InvalidArgumentException On an invalid given enumerator
112
     */
113 123
    public function attach($enumerator)
114
    {
115 123
        $enumeration = $this->enumeration;
116 123
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
117 123
    }
118
119
    /**
120
     * Detach the given enumerator
121
     * @param Enum|null|boolean|int|float|string $enumerator
122
     * @return void
123
     * @throws InvalidArgumentException On an invalid given enumerator
124
     */
125 21
    public function detach($enumerator)
126
    {
127 21
        $enumeration = $this->enumeration;
128 21
        $this->{$this->fnDoUnsetBit}($enumeration::get($enumerator)->getOrdinal());
129 21
    }
130
131
    /**
132
     * Test if the given enumerator was attached
133
     * @param Enum|null|boolean|int|float|string $enumerator
134
     * @return boolean
135
     */
136 24
    public function contains($enumerator)
137
    {
138 24
        $enumeration = $this->enumeration;
139 24
        return $this->{$this->fnDoGetBit}($enumeration::get($enumerator)->getOrdinal());
140
    }
141
142
    /* Iterator */
143
144
    /**
145
     * Get the current enumerator
146
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
147
     */
148 36
    public function current()
149
    {
150 36
        if ($this->valid()) {
151 36
            $enumeration = $this->enumeration;
152 36
            return $enumeration::byOrdinal($this->ordinal);
153
        }
154
155 6
        return null;
156
    }
157
158
    /**
159
     * Get the ordinal number of the current iterator position
160
     * @return int
161
     */
162 21
    public function key()
163
    {
164 21
        return $this->ordinal;
165
    }
166
167
    /**
168
     * Go to the next valid iterator position.
169
     * If no valid iterator position is found the iterator position will be the last possible + 1.
170
     * @return void
171
     */
172 57
    public function next()
173
    {
174
        do {
175 57
            if (++$this->ordinal >= $this->ordinalMax) {
176 18
                $this->ordinal = $this->ordinalMax;
177 18
                return;
178
            }
179 57
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
180 57
    }
181
182
    /**
183
     * Go to the first valid iterator position.
184
     * If no valid iterator position was found the iterator position will be 0.
185
     * @return void
186
     * @see doRewindBin
187
     * @see doRewindInt
188
     */
189 36
    public function rewind()
190
    {
191 36
        $this->{$this->fnDoRewind}();
192 36
    }
193
194
    /**
195
     * Go to the first valid iterator position.
196
     * If no valid iterator position was found the iterator position will be 0.
197
     *
198
     * This is the binary bitset implementation.
199
     *
200
     * @return void
201
     * @see rewind
202
     * @see doRewindInt
203
     */
204 15
    private function doRewindBin()
205
    {
206 15
        if (\ltrim($this->bitset, "\0") !== '') {
207 15
            $this->ordinal = -1;
208 15
            $this->next();
209 5
        } else {
210 3
            $this->ordinal = 0;
211
        }
212 15
    }
213
214
    /**
215
     * Go to the first valid iterator position.
216
     * If no valid iterator position was found the iterator position will be 0.
217
     *
218
     * This is the binary bitset implementation.
219
     *
220
     * @return void
221
     * @see rewind
222
     * @see doRewindBin
223
     */
224 21
    private function doRewindInt()
225
    {
226 21
        if ($this->bitset) {
227 21
            $this->ordinal = -1;
228 21
            $this->next();
229 7
        } else {
230 3
            $this->ordinal = 0;
231
        }
232 21
    }
233
234
    /**
235
     * Test if the iterator is in a valid state
236
     * @return boolean
237
     */
238 39
    public function valid()
239
    {
240 39
        return $this->ordinal !== $this->ordinalMax && $this->{$this->fnDoGetBit}($this->ordinal);
241
    }
242
243
    /* Countable */
244
245
    /**
246
     * Count the number of elements
247
     *
248
     * @return int
249
     * @see doCountBin
250
     * @see doCountInt
251
     */
252 39
    public function count()
253
    {
254 39
        return $this->{$this->fnDoCount}();
255
    }
256
257
    /**
258
     * Count the number of elements.
259
     *
260
     * This is the binary bitset implementation.
261
     *
262
     * @return int
263
     * @see count
264
     * @see doCountInt
265
     */
266 15
    private function doCountBin()
267
    {
268 15
        $count   = 0;
269 15
        $bitset  = $this->bitset;
270 15
        $byteLen = \strlen($bitset);
271 15
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
272 15
            if ($bitset[$bytePos] === "\0") {
273
                // fast skip null byte
274 15
                continue;
275
            }
276
277 15
            $ord = \ord($bitset[$bytePos]);
278 15
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
279 15
                if ($ord & (1 << $bitPos)) {
280 15
                    ++$count;
281 5
                }
282 5
            }
283 5
        }
284 15
        return $count;
285
    }
286
287
    /**
288
     * Count the number of elements.
289
     *
290
     * This is the integer bitset implementation.
291
     *
292
     * @return int
293
     * @see count
294
     * @see doCountBin
295
     */
296 24
    private function doCountInt()
297
    {
298 24
        $count  = 0;
299 24
        $bitset = $this->bitset;
300
301
        // PHP does not support right shift unsigned
302 24
        if ($bitset < 0) {
303 6
            $count = 1;
304 6
            $bitset = $bitset & \PHP_INT_MAX;
305 2
        }
306
307
        // iterate byte by byte and count set bits
308 24
        for ($i = 0; $i < \PHP_INT_SIZE; ++$i) {
309 24
            $bitPos = $i * 8;
310 24
            $bitChk = 0xff << $bitPos;
311 24
            $byte = $bitset & $bitChk;
312 24
            if ($byte) {
313 21
                $byte = $byte >> $bitPos;
314 21
                if ($byte & 0b00000001) ++$count;
315 21
                if ($byte & 0b00000010) ++$count;
316 21
                if ($byte & 0b00000100) ++$count;
317 21
                if ($byte & 0b00001000) ++$count;
318 21
                if ($byte & 0b00010000) ++$count;
319 21
                if ($byte & 0b00100000) ++$count;
320 21
                if ($byte & 0b01000000) ++$count;
321 21
                if ($byte & 0b10000000) ++$count;
322 7
            }
323
324 24
            if ($bitset <= $bitChk) {
325 21
                break;
326
            }
327 5
        }
328
329 24
        return $count;
330
    }
331
332
    /**
333
     * Check if this EnumSet is the same as other
334
     * @param EnumSet $other
335
     * @return bool
336
     */
337 9
    public function isEqual(EnumSet $other)
338
    {
339 9
        return $this->enumeration === $other->enumeration
340 9
            && $this->bitset === $other->bitset;
341
    }
342
343
    /**
344
     * Check if this EnumSet is a subset of other
345
     * @param EnumSet $other
346
     * @return bool
347
     */
348 12 View Code Duplication
    public function isSubset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
349
    {
350 12
        if ($this->enumeration !== $other->enumeration) {
351 3
            return false;
352
        }
353
354 9
        return ($this->bitset & $other->bitset) === $this->bitset;
355
    }
356
357
    /**
358
     * Check if this EnumSet is a superset of other
359
     * @param EnumSet $other
360
     * @return bool
361
     */
362 12 View Code Duplication
    public function isSuperset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
363
    {
364 12
        if ($this->enumeration !== $other->enumeration) {
365 3
            return false;
366
        }
367
368 9
        return ($this->bitset | $other->bitset) === $this->bitset;
369
    }
370
371
    /**
372
     * Produce a new set with enumerators from both this and other (this | other)
373
     *
374
     * @param EnumSet $other EnumSet of the same enumeration to produce the union
375
     * @return EnumSet
376
     */
377 6 View Code Duplication
    public function union(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
378
    {
379 6
        if ($this->enumeration !== $other->enumeration) {
380 3
            throw new InvalidArgumentException(\sprintf(
381 3
                'Other should be of the same enumeration as this %s',
382 3
                $this->enumeration
383 1
            ));
384
        }
385
386 3
        $clone = clone $this;
387 3
        $clone->bitset = $this->bitset | $other->bitset;
388 3
        return $clone;
389
    }
390
391
    /**
392
     * Produce a new set with enumerators common to both this and other (this & other)
393
     *
394
     * @param EnumSet $other EnumSet of the same enumeration to produce the intersect
395
     * @return EnumSet
396
     */
397 6 View Code Duplication
    public function intersect(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
    {
399 6
        if ($this->enumeration !== $other->enumeration) {
400 3
            throw new InvalidArgumentException(\sprintf(
401 3
                'Other should be of the same enumeration as this %s',
402 3
                $this->enumeration
403 1
            ));
404
        }
405
406 3
        $clone = clone $this;
407 3
        $clone->bitset = $this->bitset & $other->bitset;
408 3
        return $clone;
409
    }
410
411
    /**
412
     * Produce a new set with enumerators in this but not in other (this - other)
413
     *
414
     * @param EnumSet $other EnumSet of the same enumeration to produce the diff
415
     * @return EnumSet
416
     */
417 6 View Code Duplication
    public function diff(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
    {
419 6
        if ($this->enumeration !== $other->enumeration) {
420 3
            throw new InvalidArgumentException(\sprintf(
421 3
                'Other should be of the same enumeration as this %s',
422 3
                $this->enumeration
423 1
            ));
424
        }
425
426 3
        $clone = clone $this;
427 3
        $clone->bitset = $this->bitset & ~$other->bitset;
428 3
        return $clone;
429
    }
430
431
    /**
432
     * Produce a new set with enumerators in either this and other but not in both (this ^ other)
433
     *
434
     * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference
435
     * @return EnumSet
436
     */
437 6 View Code Duplication
    public function symDiff(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
438
    {
439 6
        if ($this->enumeration !== $other->enumeration) {
440 3
            throw new InvalidArgumentException(\sprintf(
441 3
                'Other should be of the same enumeration as this %s',
442 3
                $this->enumeration
443 1
            ));
444
        }
445
446 3
        $clone = clone $this;
447 3
        $clone->bitset = $this->bitset ^ $other->bitset;
448 3
        return $clone;
449
    }
450
451
    /**
452
     * Get ordinal numbers of the defined enumerators as array
453
     * @return int[]
454
     */
455 42
    public function getOrdinals()
456
    {
457 42
        return $this->{$this->fnDoGetOrdinals}();
458
    }
459
460
    /**
461
     * Get ordinal numbers of the defined enumerators as array.
462
     *
463
     * This is the binary bitset implementation.
464
     *
465
     * @return int[]
466
     * @see getOrdinals
467
     * @see goGetOrdinalsInt
468
     */
469 6
    private function doGetOrdinalsBin()
470
    {
471 6
        $ordinals = [];
472 6
        $bitset   = $this->bitset;
473 6
        $byteLen  = \strlen($bitset);
474 6
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
475 6
            if ($bitset[$bytePos] === "\0") {
476
                // fast skip null byte
477 6
                continue;
478
            }
479
480 6
            $ord = \ord($bitset[$bytePos]);
481 6
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
482 6
                if ($ord & (1 << $bitPos)) {
483 6
                    $ordinals[] = $bytePos * 8 + $bitPos;
484 2
                }
485 2
            }
486 2
        }
487 6
        return $ordinals;
488
    }
489
490
    /**
491
     * Get ordinal numbers of the defined enumerators as array.
492
     *
493
     * This is the integer bitset implementation.
494
     *
495
     * @return int[]
496
     * @see getOrdinals
497
     * @see doGetOrdinalsBin
498
     */
499 36
    private function doGetOrdinalsInt()
500
    {
501 36
        $ordinals   = [];
502 36
        $ordinalMax = $this->ordinalMax;
503 36
        $bitset     = $this->bitset;
504 36
        for ($ord = 0; $ord < $ordinalMax; ++$ord) {
505 36
            if ($bitset & (1 << $ord)) {
506 36
                $ordinals[] = $ord;
507 12
            }
508 12
        }
509 36
        return $ordinals;
510
    }
511
512
    /**
513
     * Get values of the defined enumerators as array
514
     * @return null[]|bool[]|int[]|float[]|string[]
515
     */
516 18
    public function getValues()
517
    {
518 18
        $enumeration = $this->enumeration;
519 18
        $values      = [];
520 18
        foreach ($this->getOrdinals() as $ord) {
521 18
            $values[] = $enumeration::byOrdinal($ord)->getValue();
522 6
        }
523 18
        return $values;
524
    }
525
526
    /**
527
     * Get names of the defined enumerators as array
528
     * @return string[]
529
     */
530 6
    public function getNames()
531
    {
532 6
        $enumeration = $this->enumeration;
533 6
        $names       = [];
534 6
        foreach ($this->getOrdinals() as $ord) {
535 6
            $names[] = $enumeration::byOrdinal($ord)->getName();
536 2
        }
537 6
        return $names;
538
    }
539
540
    /**
541
     * Get the defined enumerators as array
542
     * @return Enum[]
543
     */
544 6
    public function getEnumerators()
545
    {
546 6
        $enumeration = $this->enumeration;
547 6
        $enumerators = [];
548 6
        foreach ($this->getOrdinals() as $ord) {
549 6
            $enumerators[] = $enumeration::byOrdinal($ord);
550 2
        }
551 6
        return $enumerators;
552
    }
553
554
    /**
555
     * Get binary bitset in little-endian order
556
     * 
557
     * @return string
558
     */
559 18
    public function getBinaryBitsetLe()
560
    {
561 18
        return $this->{$this->fnDoGetBinaryBitsetLe}();
562
    }
563
564
    /**
565
     * Get binary bitset in little-endian order.
566
     *
567
     * This is the binary bitset implementation.
568
     *
569
     * @return string
570
     */
571 12
    private function doGetBinaryBitsetLeBin()
572
    {
573 12
        return $this->bitset;
574
    }
575
576
    /**
577
     * Get binary bitset in little-endian order.
578
     *
579
     * This is the integer bitset implementation.
580
     *
581
     * @return string
582
     */
583 6
    private function doGetBinaryBitsetLeInt()
584
    {
585 6
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
586 6
        return \substr($bin, 0, (int)\ceil($this->ordinalMax / 8));
587
    }
588
589
    /**
590
     * Set binary bitset in little-endian order
591
     *
592
     * NOTE: It resets the current position of the iterator
593
     * 
594
     * @param string $bitset
595
     * @return void
596
     * @throws InvalidArgumentException On a non string is given as Parameter
597
     */
598 39
    public function setBinaryBitsetLe($bitset)
599
    {
600 39
        if (!\is_string($bitset)) {
601 3
            throw new InvalidArgumentException('Bitset must be a string');
602
        }
603
604 36
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
605
606
        // reset the iterator position
607 18
        $this->rewind();
608 18
    }
609
610
    /**
611
     * Set binary bitset in little-endian order
612
     *
613
     * NOTE: It resets the current position of the iterator
614
     *
615
     * @param string $bitset
616
     * @return void
617
     * @throws InvalidArgumentException On a non string is given as Parameter
618
     */
619 21
    private function doSetBinaryBitsetLeBin($bitset)
620
    {
621 21
        $size   = \strlen($this->bitset);
622 21
        $sizeIn = \strlen($bitset);
623
624 21
        if ($sizeIn < $size) {
625
            // add "\0" if the given bitset is not long enough
626 3
            $bitset .= \str_repeat("\0", $size - $sizeIn);
627 19
        } elseif ($sizeIn > $size) {
628 6
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
629 3
                throw new InvalidArgumentException('Out-Of-Range bits detected');
630
            }
631 3
            $bitset = \substr($bitset, 0, $size);
632 1
        }
633
634
        // truncate out-of-range bits of last byte
635 18
        $lastByteMaxOrd = $this->ordinalMax % 8;
636 18
        if ($lastByteMaxOrd !== 0) {
637 18
            $lastByte         = $bitset[$size - 1];
638 18
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
639 18
            if ($lastByte !== $lastByteExpected) {
640 6
                throw new InvalidArgumentException('Out-Of-Range bits detected');
641
            }
642
643 12
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
644 4
        }
645
646 12
        $this->bitset = $bitset;
647 12
    }
648
649
    /**
650
     * Set binary bitset in little-endian order
651
     *
652
     * NOTE: It resets the current position of the iterator
653
     *
654
     * @param string $bitset
655
     * @return void
656
     * @throws InvalidArgumentException On a non string is given as Parameter
657
     */
658 15
    private function doSetBinaryBitsetLeInt($bitset)
659
    {
660 15
        $len = \strlen($bitset);
661 15
        $int = 0;
662 15
        for ($i = 0; $i < $len; ++$i) {
663 15
            $ord = \ord($bitset[$i]);
664
665 15
            if ($ord && $i > \PHP_INT_SIZE - 1) {
666 3
                throw new InvalidArgumentException('Out-Of-Range bits detected');
667
            }
668
669 15
            $int |= $ord << (8 * $i);
670 5
        }
671
672 12
        if ($int & (~0 << $this->ordinalMax)) {
673 6
            throw new InvalidArgumentException('Out-Of-Range bits detected');
674
        }
675
676 6
        $this->bitset = $int;
677 6
    }
678
679
    /**
680
     * Get binary bitset in big-endian order
681
     * 
682
     * @return string
683
     */
684 3
    public function getBinaryBitsetBe()
685
    {
686 3
        return \strrev($this->bitset);
687
    }
688
689
    /**
690
     * Set binary bitset in big-endian order
691
     *
692
     * NOTE: It resets the current position of the iterator
693
     * 
694
     * @param string $bitset
695
     * @return void
696
     * @throws InvalidArgumentException On a non string is given as Parameter
697
     */
698 6
    public function setBinaryBitsetBe($bitset)
699
    {
700 6
        if (!\is_string($bitset)) {
701 3
            throw new InvalidArgumentException('Bitset must be a string');
702
        }
703 3
        $this->setBinaryBitsetLe(\strrev($bitset));
704 3
    }
705
706
    /**
707
     * Get a bit at the given ordinal number
708
     *
709
     * @param int $ordinal Ordinal number of bit to get
710
     * @return boolean
711
     */
712 9
    public function getBit($ordinal)
713
    {
714 9
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
715 3
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
716
        }
717
718 6
        return $this->{$this->fnDoGetBit}($ordinal);
719
    }
720
721
    /**
722
     * Get a bit at the given ordinal number.
723
     *
724
     * This is the binary bitset implementation.
725
     *
726
     * @param int $ordinal Ordinal number of bit to get
727
     * @return boolean
728
     * @see getBit
729
     * @see doGetBitInt
730
     */
731 27
    private function doGetBitBin($ordinal)
732
    {
733 27
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
734
    }
735
736
    /**
737
     * Get a bit at the given ordinal number.
738
     *
739
     * This is the integer bitset implementation.
740
     * 
741
     * @param int $ordinal Ordinal number of bit to get
742
     * @return boolean
743
     * @see getBit
744
     * @see doGetBitBin
745
     */
746 48
    private function doGetBitInt($ordinal)
747
    {
748 48
        return (bool)($this->bitset & (1 << $ordinal));
749
    }
750
751
    /**
752
     * Set a bit at the given ordinal number
753
     *
754
     * @param int $ordinal Ordnal number of bit to set
755
     * @param bool $bit    The bit to set
756
     * @return void
757
     * @see doSetBitBin
758
     * @see doSetBitInt
759
     * @see doUnsetBin
760
     * @see doUnsetInt
761
     */
762 6
    public function setBit($ordinal, $bit)
763
    {
764 6
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
765 3
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
766
        }
767
768 3
        if ($bit) {
769 3
            $this->{$this->fnDoSetBit}($ordinal);
770 1
        } else {
771 3
            $this->{$this->fnDoUnsetBit}($ordinal);
772
        }
773 3
    }
774
775
    /**
776
     * Set a bit at the given ordinal number.
777
     *
778
     * This is the binary bitset implementation.
779
     * 
780
     * @param int $ordinal Ordnal number of bit to set
781
     * @return void
782
     * @see setBit
783
     * @see doSetBitInt
784
     */
785 21
    private function doSetBitBin($ordinal)
786
    {
787 21
        $byte = (int) ($ordinal / 8);
788 21
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
789 21
    }
790
791
    /**
792
     * Set a bit at the given ordinal number.
793
     *
794
     * This is the binary bitset implementation.
795
     *
796
     * @param int $ordinal Ordnal number of bit to set
797
     * @return void
798
     * @see setBit
799
     * @see doSetBitBin
800
     */
801 105
    private function doSetBitInt($ordinal)
802
    {
803 105
        $this->bitset = $this->bitset | (1 << $ordinal);
804 105
    }
805
806
    /**
807
     * Unset a bit at the given ordinal number.
808
     *
809
     * This is the binary bitset implementation.
810
     *
811
     * @param int $ordinal Ordinal number of bit to unset
812
     * @return void
813
     * @see setBit
814
     * @see doUnsetBitInt
815
     */
816 9
    private function doUnsetBitBin($ordinal)
817
    {
818 9
        $byte = (int) ($ordinal / 8);
819 9
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
820 9
    }
821
822
    /**
823
     * Unset a bit at the given ordinal number.
824
     *
825
     * This is the integer bitset implementation.
826
     *
827
     * @param int $ordinal Ordinal number of bit to unset
828
     * @return void
829
     * @see setBit
830
     * @see doUnsetBitBin
831
     */
832 15
    private function doUnsetBitInt($ordinal)
833
    {
834 15
        $this->bitset = $this->bitset & ~(1 << $ordinal);
835 15
    }
836
}
837