Completed
Push — feature/74 ( 239c92...b942d4 )
by Marc
02:39
created

EnumSet::doRewindBin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6666
cc 2
eloc 6
nc 2
nop 0
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
     * Defined the private method names to be called depended of
44
     * 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 123
    public function __construct($enumeration)
67
    {
68 123
        if (!is_subclass_of($enumeration, Enum::class)) {
69 2
            throw new InvalidArgumentException(sprintf(
70 2
                "%s can handle subclasses of '%s' only",
71 2
                static::class,
72 1
                Enum::class
73 1
            ));
74
        }
75
76 121
        $this->enumeration = $enumeration;
77 121
        $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 121
        if ($this->ordinalMax > PHP_INT_SIZE * 8) {
83
            // init binary bitset with zeros
84 30
            $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
85
86
            // switch internal binary bitset functions
87 30
            $this->fnDoRewind            = 'doRewindBin';
88 30
            $this->fnDoCount             = 'doCountBin';
89 30
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
90 30
            $this->fnDoGetBit            = 'doGetBitBin';
91 30
            $this->fnDoSetBit            = 'doSetBitBin';
92 30
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
93 30
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
94 30
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
95 15
        }
96 121
    }
97
98
    /**
99
     * Get the classname of the enumeration
100
     * @return string
101
     */
102 2
    public function getEnumeration()
103
    {
104 2
        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 76
    public function attach($enumerator)
114
    {
115 76
        $enumeration = $this->enumeration;
116 76
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
117 76
    }
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 14
    public function detach($enumerator)
126
    {
127 14
        $enumeration = $this->enumeration;
128 14
        $this->{$this->fnDoUnsetBit}($enumeration::get($enumerator)->getOrdinal());
129 14
    }
130
131
    /**
132
     * Test if the given enumerator was attached
133
     * @param Enum|null|boolean|int|float|string $enumerator
134
     * @return boolean
135
     */
136 16
    public function contains($enumerator)
137
    {
138 16
        $enumeration = $this->enumeration;
139 16
        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 24
    public function current()
149
    {
150 24
        if ($this->valid()) {
151 24
            $enumeration = $this->enumeration;
152 24
            return $enumeration::byOrdinal($this->ordinal);
153
        }
154
155 4
        return null;
156
    }
157
158
    /**
159
     * Get the ordinal number of the current iterator position
160
     * @return int
161
     */
162 14
    public function key()
163
    {
164 14
        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 37
    public function next()
173
    {
174
        do {
175 37
            if (++$this->ordinal >= $this->ordinalMax) {
176 12
                $this->ordinal = $this->ordinalMax;
177 12
                return;
178
            }
179 37
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
180 37
    }
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 23
    public function rewind()
190
    {
191 23
        $this->{$this->fnDoRewind}();
192 23
    }
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 10
    private function doRewindBin()
205
    {
206 10
        if (trim($this->bitset, "\0") !== '') {
207 10
            $this->ordinal = -1;
208 10
            $this->next();
209 5
        } else {
210 2
            $this->ordinal = 0;
211
        }
212 10
    }
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 13
    private function doRewindInt()
225
    {
226 13
        if ($this->bitset) {
227 13
            $this->ordinal = -1;
228 13
            $this->next();
229 6
        } else {
230 2
            $this->ordinal = 0;
231
        }
232 13
    }
233
234
    /**
235
     * Test if the iterator in a valid state
236
     * @return boolean
237
     */
238 26
    public function valid()
239
    {
240 26
        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 20
    public function count()
253
    {
254 20
        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 8
    private function doCountBin()
267
    {
268 8
        $count = 0;
269 8
        $byteLen = strlen($this->bitset);
270 8
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
271 8
            if ($this->bitset[$bytePos] === "\0") {
272
                // fast skip null byte
273 8
                continue;
274
            }
275
276 8
            $ord = ord($this->bitset[$bytePos]);
277 8
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
278 8
                if ($ord & (1 << $bitPos)) {
279 8
                    ++$count;
280 4
                }
281 4
            }
282 4
        }
283 8
        return $count;
284
    }
285
286
    /**
287
     * Count the number of elements.
288
     *
289
     * This is the integer bitset implementation.
290
     *
291
     * @return int
292
     * @see count
293
     * @see doCountBin
294
     */
295 12
    private function doCountInt()
296
    {
297 12
        $count = 0;
298 12
        $ord = 0;
299 12
        while ($ord !== $this->ordinalMax) {
300 10
            if ($this->bitset & (1 << $ord++)) {
301 10
                ++$count;
302 5
            }
303 5
        }
304 12
        return $count;
305
    }
306
307
    /**
308
     * Check if this EnumSet is the same as other
309
     * @param EnumSet $other
310
     * @return bool
311
     */
312 6
    public function isEqual(EnumSet $other)
313
    {
314 6
        return $this->enumeration === $other->enumeration
315 6
            && $this->bitset === $other->bitset;
316
    }
317
318
    /**
319
     * Check if this EnumSet is a subset of other
320
     * @param EnumSet $other
321
     * @return bool
322
     */
323 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...
324
    {
325 8
        if ($this->enumeration !== $other->enumeration) {
326 2
            return false;
327
        }
328
329 6
        return ($this->bitset & $other->bitset) === $this->bitset;
330
    }
331
332
    /**
333
     * Check if this EnumSet is a superset of other
334
     * @param EnumSet $other
335
     * @return bool
336
     */
337 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...
338
    {
339 8
        if ($this->enumeration !== $other->enumeration) {
340 2
            return false;
341
        }
342
343 6
        return ($this->bitset | $other->bitset) === $this->bitset;
344
    }
345
346
    /**
347
     * Produce a new set with enumerators from both this and other (this | other)
348
     *
349
     * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954"
Loading history...
350
     *
351
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
352
     * @return EnumSet
353
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
354 4 View Code Duplication
    public function union(...$others)
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...
355
    {
356 4
        $bitset = $this->bitset;
357 4
        foreach ($others as $other) {
358 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
359 2
                throw new InvalidArgumentException(sprintf(
360 2
                    'Others should be an instance of %s of the same enumeration as this %s',
361 2
                    __CLASS__,
362 2
                    $this->enumeration
363 1
                ));
364
            }
365
366 2
            $bitset |= $other->bitset;
367 1
        }
368
369 2
        $clone = clone $this;
370 2
        $clone->bitset = $bitset;
371 2
        return $clone;
372
    }
373
374
    /**
375
     * Produce a new set with enumerators common to both this and other (this & other)
376
     *
377
     * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954"
Loading history...
378
     *
379
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
380
     * @return EnumSet
381
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
382 4 View Code Duplication
    public function intersect(...$others)
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...
383
    {
384 4
        $bitset = $this->bitset;
385 4
        foreach ($others as $other) {
386 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
387 2
                throw new InvalidArgumentException(sprintf(
388 2
                    'Others should be an instance of %s of the same enumeration as this %s',
389 2
                    __CLASS__,
390 2
                    $this->enumeration
391 1
                ));
392
            }
393
394 2
            $bitset &= $other->bitset;
395 1
        }
396
397 2
        $clone = clone $this;
398 2
        $clone->bitset = $bitset;
399 2
        return $clone;
400
    }
401
402
    /**
403
     * Produce a new set with enumerators in this but not in other (this - other)
404
     *
405
     * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954"
Loading history...
406
     *
407
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
408
     * @return EnumSet
409
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
410 4 View Code Duplication
    public function diff(...$others)
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...
411
    {
412 4
        $clone = clone $this;
413
414 4
        if (isset($others[0])) {
415 4
            $bitset = $others[0]->bitset;
416 4
            foreach ($others as $other) {
417 4
                if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
418 2
                    throw new InvalidArgumentException(sprintf(
419 2
                        'Others should of the same enumeration as this %s',
420 2
                        __CLASS__,
421 2
                        $this->enumeration
422 1
                    ));
423
                }
424
425 2
                $bitset |= $other->bitset;
426 1
            }
427
428 2
            $clone->bitset = $this->bitset & ~$bitset;
429 1
        }
430
431 2
        return $clone;
432
    }
433
434
    /**
435
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
436
     *
437
     * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954"
Loading history...
438
     *
439
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
440
     * @return EnumSet
441
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

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