Completed
Push — feature/74 ( 1227a0...69c499 )
by Marc
02:09
created

EnumSet::symDiff()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 13
loc 13
ccs 9
cts 9
cp 1
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 2
1
<?php
2
3
namespace MabeEnum;
4
5
use Countable;
6
use Iterator;
7
use InvalidArgumentException;
8
9
/**
10
 * This EnumSet is based on a bitset of a binary string.
11
 *
12
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
 * @copyright Copyright (c) 2015 Marc Bennewitz
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
16
class EnumSet implements Iterator, Countable
17
{
18
    /**
19
     * The classname of the Enumeration
20
     * @var string
21
     */
22
    private $enumeration;
23
24
    /**
25
     * Ordinal number of current iterator position
26
     * @var int
27
     */
28
    private $ordinal = 0;
29
30
    /**
31
     * Highest possible ordinal number
32
     * @var int
33
     */
34
    private $ordinalMax;
35
36
    /**
37
     * Integer or binary (little endian) bitset
38
     * @var int|string
39
     */
40
    private $bitset = 0;
41
42
    /**#@+
43
     * Defines private method names to be called depended of how the bitset type was set too.
44
     * ... Integer or binary bitset.
45
     * ... *Int or *Bin method
46
     * 
47
     * @var string
48
     */
49
    private $fnDoRewind            = 'doRewindInt';
50
    private $fnDoCount             = 'doCountInt';
51
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';
52
    private $fnDoGetBit            = 'doGetBitInt';
53
    private $fnDoSetBit            = 'doSetBitInt';
54
    private $fnDoUnsetBit          = 'doUnsetBitInt';
55
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
56
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
57
    /**#@-*/
58
59
    /**
60
     * Constructor
61
     *
62
     * @param string $enumeration The classname of the enumeration
63
     * @throws InvalidArgumentException
64
     */
65 130
    public function __construct($enumeration)
66
    {
67 130
        if (!is_subclass_of($enumeration, Enum::class)) {
68 2
            throw new InvalidArgumentException(sprintf(
69 2
                "%s can handle subclasses of '%s' only",
70 2
                static::class,
71 1
                Enum::class
72 1
            ));
73
        }
74
75 128
        $this->enumeration = $enumeration;
76 128
        $this->ordinalMax  = count($enumeration::getConstants());
77
78
        // By default the bitset is initialized as integer bitset
79
        // in case the enumeraton has more enumerators then integer bits
80
        // we will switch this into a binary bitset
81 128
        if ($this->ordinalMax > \PHP_INT_SIZE * 8) {
82
            // init binary bitset with zeros
83 32
            $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
84
85
            // switch internal binary bitset functions
86 32
            $this->fnDoRewind            = 'doRewindBin';
87 32
            $this->fnDoCount             = 'doCountBin';
88 32
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
89 32
            $this->fnDoGetBit            = 'doGetBitBin';
90 32
            $this->fnDoSetBit            = 'doSetBitBin';
91 32
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
92 32
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
93 32
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
94 16
        }
95 128
    }
96
97
    /**
98
     * Get the classname of the enumeration
99
     * @return string
100
     */
101 2
    public function getEnumeration()
102
    {
103 2
        return $this->enumeration;
104
    }
105
106
    /**
107
     * Attach a new enumerator or overwrite an existing one
108
     * @param Enum|null|boolean|int|float|string $enumerator
109
     * @return void
110
     * @throws InvalidArgumentException On an invalid given enumerator
111
     */
112 82
    public function attach($enumerator)
113
    {
114 82
        $enumeration = $this->enumeration;
115 82
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
116 82
    }
117
118
    /**
119
     * Detach the given enumerator
120
     * @param Enum|null|boolean|int|float|string $enumerator
121
     * @return void
122
     * @throws InvalidArgumentException On an invalid given enumerator
123
     */
124 14
    public function detach($enumerator)
125
    {
126 14
        $enumeration = $this->enumeration;
127 14
        $this->{$this->fnDoUnsetBit}($enumeration::get($enumerator)->getOrdinal());
128 14
    }
129
130
    /**
131
     * Test if the given enumerator was attached
132
     * @param Enum|null|boolean|int|float|string $enumerator
133
     * @return boolean
134
     */
135 16
    public function contains($enumerator)
136
    {
137 16
        $enumeration = $this->enumeration;
138 16
        return $this->{$this->fnDoGetBit}($enumeration::get($enumerator)->getOrdinal());
139
    }
140
141
    /* Iterator */
142
143
    /**
144
     * Get the current enumerator
145
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
146
     */
147 24
    public function current()
148
    {
149 24
        if ($this->valid()) {
150 24
            $enumeration = $this->enumeration;
151 24
            return $enumeration::byOrdinal($this->ordinal);
152
        }
153
154 4
        return null;
155
    }
156
157
    /**
158
     * Get the ordinal number of the current iterator position
159
     * @return int
160
     */
161 14
    public function key()
162
    {
163 14
        return $this->ordinal;
164
    }
165
166
    /**
167
     * Go to the next valid iterator position.
168
     * If no valid iterator position is found the iterator position will be the last possible + 1.
169
     * @return void
170
     */
171 38
    public function next()
172
    {
173
        do {
174 38
            if (++$this->ordinal >= $this->ordinalMax) {
175 12
                $this->ordinal = $this->ordinalMax;
176 12
                return;
177
            }
178 38
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
179 38
    }
180
181
    /**
182
     * Go to the first valid iterator position.
183
     * If no valid iterator position was found the iterator position will be 0.
184
     * @return void
185
     * @see doRewindBin
186
     * @see doRewindInt
187
     */
188 24
    public function rewind()
189
    {
190 24
        $this->{$this->fnDoRewind}();
191 24
    }
192
193
    /**
194
     * Go to the first valid iterator position.
195
     * If no valid iterator position was found the iterator position will be 0.
196
     *
197
     * This is the binary bitset implementation.
198
     *
199
     * @return void
200
     * @see rewind
201
     * @see doRewindInt
202
     */
203 10
    private function doRewindBin()
204
    {
205 10
        if (trim($this->bitset, "\0") !== '') {
206 10
            $this->ordinal = -1;
207 10
            $this->next();
208 5
        } else {
209 2
            $this->ordinal = 0;
210
        }
211 10
    }
212
213
    /**
214
     * Go to the first valid iterator position.
215
     * If no valid iterator position was found the iterator position will be 0.
216
     *
217
     * This is the binary bitset implementation.
218
     *
219
     * @return void
220
     * @see rewind
221
     * @see doRewindBin
222
     */
223 14
    private function doRewindInt()
224
    {
225 14
        if ($this->bitset) {
226 14
            $this->ordinal = -1;
227 14
            $this->next();
228 7
        } else {
229 2
            $this->ordinal = 0;
230
        }
231 14
    }
232
233
    /**
234
     * Test if the iterator in a valid state
235
     * @return boolean
236
     */
237 26
    public function valid()
238
    {
239 26
        return $this->ordinal !== $this->ordinalMax && $this->{$this->fnDoGetBit}($this->ordinal);
240
    }
241
242
    /* Countable */
243
244
    /**
245
     * Count the number of elements
246
     *
247
     * @return int
248
     * @see doCountBin
249
     * @see doCountInt
250
     */
251 26
    public function count()
252
    {
253 26
        return $this->{$this->fnDoCount}();
254
    }
255
256
    /**
257
     * Count the number of elements.
258
     *
259
     * This is the binary bitset implementation.
260
     *
261
     * @return int
262
     * @see count
263
     * @see doCountInt
264
     */
265 10
    private function doCountBin()
266
    {
267 10
        $count = 0;
268 10
        $byteLen = strlen($this->bitset);
269 10
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
270 10
            if ($this->bitset[$bytePos] === "\0") {
271
                // fast skip null byte
272 10
                continue;
273
            }
274
275 10
            $ord = ord($this->bitset[$bytePos]);
276 10
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
277 10
                if ($ord & (1 << $bitPos)) {
278 10
                    ++$count;
279 5
                }
280 5
            }
281 5
        }
282 10
        return $count;
283
    }
284
285
    /**
286
     * Count the number of elements.
287
     *
288
     * This is the integer bitset implementation.
289
     *
290
     * @return int
291
     * @see count
292
     * @see doCountBin
293
     */
294 16
    private function doCountInt()
295
    {
296 16
        $count  = 0;
297 16
        $bitset = $this->bitset;
298
299
        // PHP does not support right shift unsigned
300 16
        if ($bitset < 0) {
301 4
            $count = 1;
302 4
            $bitset = $bitset & \PHP_INT_MAX;
303 2
        }
304
305
        // iterate byte by byte and count set bits
306 16
        for ($i = 0; $i < \PHP_INT_SIZE; ++$i) {
307 16
            $bitPos = $i * 8;
308 16
            $bitChk = 0xff << $bitPos;
309 16
            $byte = $bitset & $bitChk;
310 16
            if ($byte) {
311 14
                $byte = $byte >> $bitPos;
312 14
                if ($byte & 0b00000001) ++$count;
313 14
                if ($byte & 0b00000010) ++$count;
314 14
                if ($byte & 0b00000100) ++$count;
315 14
                if ($byte & 0b00001000) ++$count;
316 14
                if ($byte & 0b00010000) ++$count;
317 14
                if ($byte & 0b00100000) ++$count;
318 14
                if ($byte & 0b01000000) ++$count;
319 14
                if ($byte & 0b10000000) ++$count;
320 7
            }
321
322 16
            if ($bitset <= $bitChk) {
323 14
                break;
324
            }
325 5
        }
326
327 16
        return $count;
328
    }
329
330
    /**
331
     * Check if this EnumSet is the same as other
332
     * @param EnumSet $other
333
     * @return bool
334
     */
335 6
    public function isEqual(EnumSet $other)
336
    {
337 6
        return $this->enumeration === $other->enumeration
338 6
            && $this->bitset === $other->bitset;
339
    }
340
341
    /**
342
     * Check if this EnumSet is a subset of other
343
     * @param EnumSet $other
344
     * @return bool
345
     */
346 8 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...
347
    {
348 8
        if ($this->enumeration !== $other->enumeration) {
349 2
            return false;
350
        }
351
352 6
        return ($this->bitset & $other->bitset) === $this->bitset;
353
    }
354
355
    /**
356
     * Check if this EnumSet is a superset of other
357
     * @param EnumSet $other
358
     * @return bool
359
     */
360 8 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...
361
    {
362 8
        if ($this->enumeration !== $other->enumeration) {
363 2
            return false;
364
        }
365
366 6
        return ($this->bitset | $other->bitset) === $this->bitset;
367
    }
368
369
    /**
370
     * Produce a new set with enumerators from both this and other (this | other)
371
     *
372
     * @param EnumSet $other EnumSet of the same enumeration to produce the union
373
     * @return EnumSet
374
     */
375 4 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...
376
    {
377 4
        if ($this->enumeration !== $other->enumeration) {
378 2
            throw new InvalidArgumentException(sprintf(
379 2
                'Other should be of the same enumeration as this %s',
380 2
                $this->enumeration
381 1
            ));
382
        }
383
384 2
        $clone = clone $this;
385 2
        $clone->bitset = $this->bitset | $other->bitset;
386 2
        return $clone;
387
    }
388
389
    /**
390
     * Produce a new set with enumerators common to both this and other (this & other)
391
     *
392
     * @param EnumSet $other EnumSet of the same enumeration to produce the intersect
393
     * @return EnumSet
394
     */
395 4 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...
396
    {
397 4
        if ($this->enumeration !== $other->enumeration) {
398 2
            throw new InvalidArgumentException(sprintf(
399 2
                'Other should be of the same enumeration as this %s',
400 2
                $this->enumeration
401 1
            ));
402
        }
403
404 2
        $clone = clone $this;
405 2
        $clone->bitset = $this->bitset & $other->bitset;
406 2
        return $clone;
407
    }
408
409
    /**
410
     * Produce a new set with enumerators in this but not in other (this - other)
411
     *
412
     * @param EnumSet $other EnumSet of the same enumeration to produce the diff
413
     * @return EnumSet
414
     */
415 4 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...
416
    {
417 4
        if ($this->enumeration !== $other->enumeration) {
418 2
            throw new InvalidArgumentException(sprintf(
419 2
                'Other should be of the same enumeration as this %s',
420 2
                $this->enumeration
421 1
            ));
422
        }
423
424 2
        $clone = clone $this;
425 2
        $clone->bitset = $this->bitset & ~$other->bitset;
426 2
        return $clone;
427
    }
428
429
    /**
430
     * Produce a new set with enumerators in either this and other but not in both (this ^ other)
431
     *
432
     * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference
433
     * @return EnumSet
434
     */
435 4 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...
436
    {
437 4
        if ($this->enumeration !== $other->enumeration) {
438 2
            throw new InvalidArgumentException(sprintf(
439 2
                'Other should be of the same enumeration as this %s',
440 2
                $this->enumeration
441 1
            ));
442
        }
443
444 2
        $clone = clone $this;
445 2
        $clone->bitset = $this->bitset ^ $other->bitset;
446 2
        return $clone;
447
    }
448
449
    /**
450
     * Get ordinal numbers of the defined enumerators as array
451
     * @return int[]
452
     */
453 28
    public function getOrdinals()
454
    {
455 28
        return $this->{$this->fnDoGetOrdinals}();
456
    }
457
458
    /**
459
     * Get ordinal numbers of the defined enumerators as array.
460
     *
461
     * This is the binary bitset implementation.
462
     *
463
     * @return int[]
464
     * @see getOrdinals
465
     * @see goGetOrdinalsInt
466
     */
467 4
    private function doGetOrdinalsBin()
468
    {
469 4
        $ordinals = [];
470 4
        $byteLen = strlen($this->bitset);
471 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
472 4
            if ($this->bitset[$bytePos] === "\0") {
473
                // fast skip null byte
474 4
                continue;
475
            }
476
477 4
            $ord = ord($this->bitset[$bytePos]);
478 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
479 4
                if ($ord & (1 << $bitPos)) {
480 4
                    $ordinals[] = $bytePos * 8 + $bitPos;
481 2
                }
482 2
            }
483 2
        }
484 4
        return $ordinals;
485
    }
486
487
    /**
488
     * Get ordinal numbers of the defined enumerators as array.
489
     *
490
     * This is the integer bitset implementation.
491
     *
492
     * @return int[]
493
     * @see getOrdinals
494
     * @see doGetOrdinalsBin
495
     */
496 24
    private function doGetOrdinalsInt()
497
    {
498 24
        $ordinals = [];
499 24
        for ($ord = 0; $ord < $this->ordinalMax; ++$ord) {
500 24
            if ($this->bitset & (1 << $ord)) {
501 24
                $ordinals[] = $ord;
502 12
            }
503 12
        }
504 24
        return $ordinals;
505
    }
506
507
    /**
508
     * Get values of the defined enumerators as array
509
     * @return null[]|bool[]|int[]|float[]|string[]
510
     */
511 12
    public function getValues()
512
    {
513 12
        $enumeration = $this->enumeration;
514 12
        $values      = [];
515 12
        foreach ($this->getOrdinals() as $ord) {
516 12
            $values[] = $enumeration::byOrdinal($ord)->getValue();
517 6
        }
518 12
        return $values;
519
    }
520
521
    /**
522
     * Get names of the defined enumerators as array
523
     * @return string[]
524
     */
525 4
    public function getNames()
526
    {
527 4
        $enumeration = $this->enumeration;
528 4
        $names       = [];
529 4
        foreach ($this->getOrdinals() as $ord) {
530 4
            $names[] = $enumeration::byOrdinal($ord)->getName();
531 2
        }
532 4
        return $names;
533
    }
534
535
    /**
536
     * Get the defined enumerators as array
537
     * @return Enum[]
538
     */
539 4
    public function getEnumerators()
540
    {
541 4
        $enumeration = $this->enumeration;
542 4
        $enumerators = [];
543 4
        foreach ($this->getOrdinals() as $ord) {
544 4
            $enumerators[] = $enumeration::byOrdinal($ord);
545 2
        }
546 4
        return $enumerators;
547
    }
548
549
    /**
550
     * Get binary bitset in little-endian order
551
     * 
552
     * @return string
553
     */
554 12
    public function getBinaryBitsetLe()
555
    {
556 12
        return $this->{$this->fnDoGetBinaryBitsetLe}();
557
    }
558
559
    /**
560
     * Get binary bitset in little-endian order.
561
     *
562
     * This is the binary bitset implementation.
563
     *
564
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
565
     */
566 8
    private function doGetBinaryBitsetLeBin()
567
    {
568 8
        return $this->bitset;
569
    }
570
571
    /**
572
     * Get binary bitset in little-endian order.
573
     *
574
     * This is the integer bitset implementation.
575
     *
576
     * @return string
577
     */
578 4
    private function doGetBinaryBitsetLeInt()
579
    {
580 4
        $bin = pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
581 4
        return substr($bin, 0, ceil($this->ordinalMax / 8));
582
    }
583
584
    /**
585
     * Set binary bitset in little-endian order
586
     *
587
     * NOTE: It resets the current position of the iterator
588
     * 
589
     * @param string $bitset
590
     * @return void
591
     * @throws InvalidArgumentException On a non string is given as Parameter
592
     */
593 26
    public function setBinaryBitsetLe($bitset)
594
    {
595 26
        if (!is_string($bitset)) {
596 2
            throw new InvalidArgumentException('Bitset must be a string');
597
        }
598
599 24
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
600
601
        // reset the iterator position
602 12
        $this->rewind();
603 12
    }
604
605
    /**
606
     * Set binary bitset in little-endian order
607
     *
608
     * NOTE: It resets the current position of the iterator
609
     *
610
     * @param string $bitset
611
     * @return void
612
     * @throws InvalidArgumentException On a non string is given as Parameter
613
     */
614 14
    private function doSetBinaryBitsetLeBin($bitset)
615
    {
616 14
        $size   = strlen($this->bitset);
617 14
        $sizeIn = strlen($bitset);
618
619 14
        if ($sizeIn < $size) {
620
            // add "\0" if the given bitset is not long enough
621 2
            $bitset .= str_repeat("\0", $size - $sizeIn);
622 13
        } elseif ($sizeIn > $size) {
623 4
            if (trim(substr($bitset, $size), "\0") !== '') {
624 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
625
            }
626 2
            $bitset = substr($bitset, 0, $size);
627 1
        }
628
629
        // truncate out-of-range bits of last byte
630 12
        $lastByteMaxOrd = $this->ordinalMax % 8;
631 12
        if ($lastByteMaxOrd !== 0) {
632 12
            $lastByte         = $bitset[$size - 1];
633 12
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
634 12
            if ($lastByte !== $lastByteExpected) {
635 4
                throw new InvalidArgumentException('Out-Of-Range bits detected');
636
            }
637
638 8
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
639 4
        }
640
641 8
        $this->bitset = $bitset;
642 8
    }
643
644
    /**
645
     * Set binary bitset in little-endian order
646
     *
647
     * NOTE: It resets the current position of the iterator
648
     *
649
     * @param string $bitset
650
     * @return void
651
     * @throws InvalidArgumentException On a non string is given as Parameter
652
     */
653 10
    private function doSetBinaryBitsetLeInt($bitset)
654
    {
655 10
        $len = strlen($bitset);
656 10
        $int = 0;
657 10
        for ($i = 0; $i < $len; ++$i) {
658 10
            $ord = ord($bitset[$i]);
659
660 10
            if ($ord && $i > \PHP_INT_SIZE - 1) {
661 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
662
            }
663
664 10
            $int |= $ord << (8 * $i);
665 5
        }
666
667 8
        if ($int & (~0 << $this->ordinalMax)) {
668 4
            throw new InvalidArgumentException('Out-Of-Range bits detected');
669
        }
670
671 4
        $this->bitset = $int;
672 4
    }
673
674
    /**
675
     * Get binary bitset in big-endian order
676
     * 
677
     * @return string
678
     */
679 2
    public function getBinaryBitsetBe()
680
    {
681 2
        return strrev($this->bitset);
682
    }
683
684
    /**
685
     * Set binary bitset in big-endian order
686
     *
687
     * NOTE: It resets the current position of the iterator
688
     * 
689
     * @param string $bitset
690
     * @return void
691
     * @throws InvalidArgumentException On a non string is given as Parameter
692
     */
693 4
    public function setBinaryBitsetBe($bitset)
694
    {
695 4
        if (!is_string($bitset)) {
696 2
            throw new InvalidArgumentException('Bitset must be a string');
697
        }
698 2
        $this->setBinaryBitsetLe(strrev($bitset));
699 2
    }
700
701
    /**
702
     * Get a bit at the given ordinal number
703
     *
704
     * @param int $ordinal Ordinal number of bit to get
705
     * @return boolean
706
     */
707 6
    public function getBit($ordinal)
708
    {
709 6
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
710 2
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $this instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
711
        }
712
713 4
        return $this->{$this->fnDoGetBit}($ordinal);
714
    }
715
716
    /**
717
     * Get a bit at the given ordinal number.
718
     *
719
     * This is the binary bitset implementation.
720
     *
721
     * @param int $ordinal Ordinal number of bit to get
722
     * @return boolean
723
     * @see getBit
724
     * @see doGetBitInt
725
     */
726 18
    private function doGetBitBin($ordinal)
727
    {
728 18
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
729
    }
730
731
    /**
732
     * Get a bit at the given ordinal number.
733
     *
734
     * This is the integer bitset implementation.
735
     * 
736
     * @param int $ordinal Ordinal number of bit to get
737
     * @return boolean
738
     * @see getBit
739
     * @see doGetBitBin
740
     */
741 32
    private function doGetBitInt($ordinal)
742
    {
743 32
        return (bool)($this->bitset & (1 << $ordinal));
744
    }
745
746
    /**
747
     * Set a bit at the given ordinal number
748
     *
749
     * @param int $ordinal Ordnal number of bit to set
750
     * @param bool $bit    The bit to set
751
     * @return void
752
     * @see doSetBitBin
753
     * @see doSetBitInt
754
     * @see doUnsetBin
755
     * @see doUnsetInt
756
     */
757 4
    public function setBit($ordinal, $bit)
758
    {
759 4
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
760 2
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $this instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
761
        }
762
763 2
        if ($bit) {
764 2
            $this->{$this->fnDoSetBit}($ordinal);
765 1
        } else {
766 2
            $this->{$this->fnDoUnsetBit}($ordinal);
767
        }
768 2
    }
769
770
    /**
771
     * Set a bit at the given ordinal number.
772
     *
773
     * This is the binary bitset implementation.
774
     * 
775
     * @param int $ordinal Ordnal number of bit to set
776
     * @return void
777
     * @see setBit
778
     * @see doSetBitInt
779
     */
780 14
    private function doSetBitBin($ordinal)
781
    {
782 14
        $byte = (int) ($ordinal / 8);
783 14
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
784 14
    }
785
786
    /**
787
     * Set a bit at the given ordinal number.
788
     *
789
     * This is the binary bitset implementation.
790
     *
791
     * @param int $ordinal Ordnal number of bit to set
792
     * @return void
793
     * @see setBit
794
     * @see doSetBitBin
795
     */
796 70
    private function doSetBitInt($ordinal)
797
    {
798 70
        $this->bitset = $this->bitset | (1 << $ordinal);
799 70
    }
800
801
    /**
802
     * Unset a bit at the given ordinal number.
803
     *
804
     * This is the binary bitset implementation.
805
     *
806
     * @param int $ordinal Ordinal number of bit to unset
807
     * @return void
808
     * @see setBit
809
     * @see doUnsetBitInt
810
     */
811 6
    private function doUnsetBitBin($ordinal)
812
    {
813 6
        $byte = (int) ($ordinal / 8);
814 6
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
815 6
    }
816
817
    /**
818
     * Unset a bit at the given ordinal number.
819
     *
820
     * This is the integer bitset implementation.
821
     *
822
     * @param int $ordinal Ordinal number of bit to unset
823
     * @return void
824
     * @see setBit
825
     * @see doUnsetBitBin
826
     */
827 10
    private function doUnsetBitInt($ordinal)
828
    {
829 10
        $this->bitset = $this->bitset & ~(1 << $ordinal);
830 10
    }
831
}
832