Completed
Pull Request — master (#86)
by Marc
04:08 queued 02:52
created

EnumSet::doGetBinaryBitsetLeBin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 130
    public function __construct($enumeration)
67
    {
68 130
        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 128
        $this->enumeration = $enumeration;
77 128
        $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 128
        if ($this->ordinalMax > PHP_INT_SIZE * 8) {
83
            // init binary bitset with zeros
84 32
            $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
85
86
            // switch internal binary bitset functions
87 32
            $this->fnDoRewind            = 'doRewindBin';
88 32
            $this->fnDoCount             = 'doCountBin';
89 32
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
90 32
            $this->fnDoGetBit            = 'doGetBitBin';
91 32
            $this->fnDoSetBit            = 'doSetBitBin';
92 32
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
93 32
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
94 32
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
95 16
        }
96 128
    }
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 82
    public function attach($enumerator)
114
    {
115 82
        $enumeration = $this->enumeration;
116 82
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
117 82
    }
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 38
    public function next()
173
    {
174
        do {
175 38
            if (++$this->ordinal >= $this->ordinalMax) {
176 12
                $this->ordinal = $this->ordinalMax;
177 12
                return;
178
            }
179 38
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
180 38
    }
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 24
    public function rewind()
190
    {
191 24
        $this->{$this->fnDoRewind}();
192 24
    }
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 14
    private function doRewindInt()
225
    {
226 14
        if ($this->bitset) {
227 14
            $this->ordinal = -1;
228 14
            $this->next();
229 7
        } else {
230 2
            $this->ordinal = 0;
231
        }
232 14
    }
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 26
    public function count()
253
    {
254 26
        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 10
    private function doCountBin()
267
    {
268 10
        $count = 0;
269 10
        $byteLen = strlen($this->bitset);
270 10
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
271 10
            if ($this->bitset[$bytePos] === "\0") {
272
                // fast skip null byte
273 10
                continue;
274
            }
275
276 10
            $ord = ord($this->bitset[$bytePos]);
277 10
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
278 10
                if ($ord & (1 << $bitPos)) {
279 10
                    ++$count;
280 5
                }
281 5
            }
282 5
        }
283 10
        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 16
    private function doCountInt()
296
    {
297 16
        $count  = 0;
298 16
        $bitset = $this->bitset;
299
300
        // PHP does not support right shift unsigned
301 16
        if ($bitset < 0) {
302 4
            $count = 1;
303 4
            $bitset = $bitset & \PHP_INT_MAX;
304 2
        }
305
306
        // iterate byte by byte and count set bits
307 16
        for ($i = 0; $i < \PHP_INT_SIZE; ++$i) {
308 16
            $bitPos = $i * 8;
309 16
            $bitChk = 0xff << $bitPos;
310 16
            $byte = $bitset & $bitChk;
311 16
            if ($byte) {
312 14
                $byte = $byte >> $bitPos;
313 14
                if ($byte & 0b00000001) ++$count;
314 14
                if ($byte & 0b00000010) ++$count;
315 14
                if ($byte & 0b00000100) ++$count;
316 14
                if ($byte & 0b00001000) ++$count;
317 14
                if ($byte & 0b00010000) ++$count;
318 14
                if ($byte & 0b00100000) ++$count;
319 14
                if ($byte & 0b01000000) ++$count;
320 14
                if ($byte & 0b10000000) ++$count;
321 7
            }
322
323 16
            if ($bitset <= $bitChk) {
324 14
                break;
325
            }
326 5
        }
327
328 16
        return $count;
329
    }
330
331
    /**
332
     * Check if this EnumSet is the same as other
333
     * @param EnumSet $other
334
     * @return bool
335
     */
336 6
    public function isEqual(EnumSet $other)
337
    {
338 6
        return $this->enumeration === $other->enumeration
339 6
            && $this->bitset === $other->bitset;
340
    }
341
342
    /**
343
     * Check if this EnumSet is a subset of other
344
     * @param EnumSet $other
345
     * @return bool
346
     */
347 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...
348
    {
349 8
        if ($this->enumeration !== $other->enumeration) {
350 2
            return false;
351
        }
352
353 6
        return ($this->bitset & $other->bitset) === $this->bitset;
354
    }
355
356
    /**
357
     * Check if this EnumSet is a superset of other
358
     * @param EnumSet $other
359
     * @return bool
360
     */
361 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...
362
    {
363 8
        if ($this->enumeration !== $other->enumeration) {
364 2
            return false;
365
        }
366
367 6
        return ($this->bitset | $other->bitset) === $this->bitset;
368
    }
369
370
    /**
371
     * Produce a new set with enumerators from both this and other (this | other)
372
     *
373
     * 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...
374
     *
375
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
376
     * @return EnumSet
377
     */
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...
378 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...
379
    {
380 4
        $bitset = $this->bitset;
381 4
        foreach ($others as $other) {
382 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
383 2
                throw new InvalidArgumentException(sprintf(
384 2
                    'Others should be an instance of %s of the same enumeration as this %s',
385 2
                    __CLASS__,
386 2
                    $this->enumeration
387 1
                ));
388
            }
389
390 2
            $bitset |= $other->bitset;
391 1
        }
392
393 2
        $clone = clone $this;
394 2
        $clone->bitset = $bitset;
395 2
        return $clone;
396
    }
397
398
    /**
399
     * Produce a new set with enumerators common to both this and other (this & other)
400
     *
401
     * 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...
402
     *
403
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
404
     * @return EnumSet
405
     */
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...
406 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...
407
    {
408 4
        $bitset = $this->bitset;
409 4
        foreach ($others as $other) {
410 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
411 2
                throw new InvalidArgumentException(sprintf(
412 2
                    'Others should be an instance of %s of the same enumeration as this %s',
413 2
                    __CLASS__,
414 2
                    $this->enumeration
415 1
                ));
416
            }
417
418 2
            $bitset &= $other->bitset;
419 1
        }
420
421 2
        $clone = clone $this;
422 2
        $clone->bitset = $bitset;
423 2
        return $clone;
424
    }
425
426
    /**
427
     * Produce a new set with enumerators in this but not in other (this - other)
428
     *
429
     * 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...
430
     *
431
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
432
     * @return EnumSet
433
     */
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...
434 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...
435
    {
436 4
        $clone = clone $this;
437
438 4
        if (isset($others[0])) {
439 4
            $bitset = $others[0]->bitset;
440 4
            foreach ($others as $other) {
441 4
                if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
442 2
                    throw new InvalidArgumentException(sprintf(
443 2
                        'Others should of the same enumeration as this %s',
444 2
                        __CLASS__,
445 2
                        $this->enumeration
446 1
                    ));
447
                }
448
449 2
                $bitset |= $other->bitset;
450 1
            }
451
452 2
            $clone->bitset = $this->bitset & ~$bitset;
453 1
        }
454
455 2
        return $clone;
456
    }
457
458
    /**
459
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
460
     *
461
     * 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...
462
     *
463
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
464
     * @return EnumSet
465
     */
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...
466 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...
467
    {
468 4
        $clone = clone $this;
469
470 4
        if (isset($others[0])) {
471 4
            $bitset = $others[0]->bitset;
472 4
            foreach ($others as $other) {
473 4
                if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
474 2
                    throw new InvalidArgumentException(sprintf(
475 2
                        'Others should be an instance of %s of the same enumeration as this %s',
476 2
                        __CLASS__,
477 2
                        $this->enumeration
478 1
                    ));
479
                }
480
481 2
                $bitset |= $other->bitset;
482 1
            }
483
484 2
            $clone->bitset = $this->bitset ^ $bitset;
485 1
        }
486
487 2
        return $clone;
488
    }
489
490
    /**
491
     * Get ordinal numbers of the defined enumerators as array
492
     * @return int[]
493
     */
494 28
    public function getOrdinals()
495
    {
496 28
        return $this->{$this->fnDoGetOrdinals}();
497
    }
498
499
    /**
500
     * Get ordinal numbers of the defined enumerators as array.
501
     *
502
     * This is the binary bitset implementation.
503
     *
504
     * @return int[]
505
     * @see getOrdinals
506
     * @see goGetOrdinalsInt
507
     */
508 4
    private function doGetOrdinalsBin()
509
    {
510 4
        $ordinals = [];
511 4
        $byteLen = strlen($this->bitset);
512 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
513 4
            if ($this->bitset[$bytePos] === "\0") {
514
                // fast skip null byte
515 4
                continue;
516
            }
517
518 4
            $ord = ord($this->bitset[$bytePos]);
519 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
520 4
                if ($ord & (1 << $bitPos)) {
521 4
                    $ordinals[] = $bytePos * 8 + $bitPos;
522 2
                }
523 2
            }
524 2
        }
525 4
        return $ordinals;
526
    }
527
528
    /**
529
     * Get ordinal numbers of the defined enumerators as array.
530
     *
531
     * This is the integer bitset implementation.
532
     *
533
     * @return int[]
534
     * @see getOrdinals
535
     * @see doGetOrdinalsBin
536
     */
537 24
    private function doGetOrdinalsInt()
538
    {
539 24
        $ordinals = [];
540 24
        for ($ord = 0; $ord < $this->ordinalMax; ++$ord) {
541 24
            if ($this->bitset & (1 << $ord)) {
542 24
                $ordinals[] = $ord;
543 12
            }
544 12
        }
545 24
        return $ordinals;
546
    }
547
548
    /**
549
     * Get values of the defined enumerators as array
550
     * @return null[]|bool[]|int[]|float[]|string[]
551
     */
552 12
    public function getValues()
553
    {
554 12
        $enumeration = $this->enumeration;
555 12
        $values      = [];
556 12
        foreach ($this->getOrdinals() as $ord) {
557 12
            $values[] = $enumeration::byOrdinal($ord)->getValue();
558 6
        }
559 12
        return $values;
560
    }
561
562
    /**
563
     * Get names of the defined enumerators as array
564
     * @return string[]
565
     */
566 4
    public function getNames()
567
    {
568 4
        $enumeration = $this->enumeration;
569 4
        $names       = [];
570 4
        foreach ($this->getOrdinals() as $ord) {
571 4
            $names[] = $enumeration::byOrdinal($ord)->getName();
572 2
        }
573 4
        return $names;
574
    }
575
576
    /**
577
     * Get the defined enumerators as array
578
     * @return Enum[]
579
     */
580 4
    public function getEnumerators()
581
    {
582 4
        $enumeration = $this->enumeration;
583 4
        $enumerators = [];
584 4
        foreach ($this->getOrdinals() as $ord) {
585 4
            $enumerators[] = $enumeration::byOrdinal($ord);
586 2
        }
587 4
        return $enumerators;
588
    }
589
590
    /**
591
     * Get binary bitset in little-endian order
592
     * 
593
     * @return string
594
     */
595 12
    public function getBinaryBitsetLe()
596
    {
597 12
        return $this->{$this->fnDoGetBinaryBitsetLe}();
598
    }
599
600
    /**
601
     * Get binary bitset in little-endian order.
602
     *
603
     * This is the binary bitset implementation.
604
     *
605
     * @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...
606
     */
607 8
    private function doGetBinaryBitsetLeBin()
608
    {
609 8
        return $this->bitset;
610
    }
611
612
    /**
613
     * Get binary bitset in little-endian order.
614
     *
615
     * This is the integer bitset implementation.
616
     *
617
     * @return string
618
     */
619 4
    private function doGetBinaryBitsetLeInt()
620
    {
621 4
        $bin = pack(PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
622 4
        return substr($bin, 0, ceil($this->ordinalMax / 8));
623
    }
624
625
    /**
626
     * Set binary bitset in little-endian order
627
     *
628
     * NOTE: It resets the current position of the iterator
629
     * 
630
     * @param string $bitset
631
     * @return void
632
     * @throws InvalidArgumentException On a non string is given as Parameter
633
     */
634 26
    public function setBinaryBitsetLe($bitset)
635
    {
636 26
        if (!is_string($bitset)) {
637 2
            throw new InvalidArgumentException('Bitset must be a string');
638
        }
639
640 24
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
641
642
        // reset the iterator position
643 12
        $this->rewind();
644 12
    }
645
646
    /**
647
     * Set binary bitset in little-endian order
648
     *
649
     * NOTE: It resets the current position of the iterator
650
     *
651
     * @param string $bitset
652
     * @return void
653
     * @throws InvalidArgumentException On a non string is given as Parameter
654
     */
655 14
    private function doSetBinaryBitsetLeBin($bitset)
656
    {
657 14
        $size   = strlen($this->bitset);
658 14
        $sizeIn = strlen($bitset);
659
660 14
        if ($sizeIn < $size) {
661
            // add "\0" if the given bitset is not long enough
662 2
            $bitset .= str_repeat("\0", $size - $sizeIn);
663 13
        } elseif ($sizeIn > $size) {
664 4
            if (trim(substr($bitset, $size), "\0") !== '') {
665 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
666
            }
667 2
            $bitset = substr($bitset, 0, $size);
668 1
        }
669
670
        // truncate out-of-range bits of last byte
671 12
        $lastByteMaxOrd = $this->ordinalMax % 8;
672 12
        if ($lastByteMaxOrd !== 0) {
673 12
            $lastByte         = $bitset[$size - 1];
674 12
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
675 12
            if ($lastByte !== $lastByteExpected) {
676 4
                throw new InvalidArgumentException('Out-Of-Range bits detected');
677
            }
678
679 8
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
680 4
        }
681
682 8
        $this->bitset = $bitset;
683 8
    }
684
685
    /**
686
     * Set binary bitset in little-endian order
687
     *
688
     * NOTE: It resets the current position of the iterator
689
     *
690
     * @param string $bitset
691
     * @return void
692
     * @throws InvalidArgumentException On a non string is given as Parameter
693
     */
694 10
    private function doSetBinaryBitsetLeInt($bitset)
695
    {
696 10
        $len = strlen($bitset);
697 10
        $int = 0;
698 10
        for ($i = 0; $i < $len; ++$i) {
699 10
            $ord = ord($bitset[$i]);
700
701 10
            if ($ord && $i > PHP_INT_SIZE - 1) {
702 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
703
            }
704
705 10
            $int |= $ord << (8 * $i);
706 5
        }
707
708 8
        if ($int & (~0 << $this->ordinalMax)) {
709 4
            throw new InvalidArgumentException('Out-Of-Range bits detected');
710
        }
711
712 4
        $this->bitset = $int;
713 4
    }
714
715
    /**
716
     * Get binary bitset in big-endian order
717
     * 
718
     * @return string
719
     */
720 2
    public function getBinaryBitsetBe()
721
    {
722 2
        return strrev($this->bitset);
723
    }
724
725
    /**
726
     * Set binary bitset in big-endian order
727
     *
728
     * NOTE: It resets the current position of the iterator
729
     * 
730
     * @param string $bitset
731
     * @return void
732
     * @throws InvalidArgumentException On a non string is given as Parameter
733
     */
734 4
    public function setBinaryBitsetBe($bitset)
735
    {
736 4
        if (!is_string($bitset)) {
737 2
            throw new InvalidArgumentException('Bitset must be a string');
738
        }
739 2
        $this->setBinaryBitsetLe(strrev($bitset));
740 2
    }
741
742
    /**
743
     * Get a bit at the given ordinal number
744
     *
745
     * @param int $ordinal Ordinal number of bit to get
746
     * @return boolean
747
     */
748 6
    public function getBit($ordinal)
749
    {
750 6
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
751 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...
752
        }
753
754 4
        return $this->{$this->fnDoGetBit}($ordinal);
755
    }
756
757
    /**
758
     * Get a bit at the given ordinal number.
759
     *
760
     * This is the binary bitset implementation.
761
     *
762
     * @param int $ordinal Ordinal number of bit to get
763
     * @return boolean
764
     * @see getBit
765
     * @see doGetBitInt
766
     */
767 18
    private function doGetBitBin($ordinal)
768
    {
769 18
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
770
    }
771
772
    /**
773
     * Get a bit at the given ordinal number.
774
     *
775
     * This is the integer bitset implementation.
776
     * 
777
     * @param int $ordinal Ordinal number of bit to get
778
     * @return boolean
779
     * @see getBit
780
     * @see doGetBitBin
781
     */
782 32
    private function doGetBitInt($ordinal)
783
    {
784 32
        return (bool)($this->bitset & (1 << $ordinal));
785
    }
786
787
    /**
788
     * Set a bit at the given ordinal number
789
     *
790
     * @param int $ordinal Ordnal number of bit to set
791
     * @param bool $bit    The bit to set
792
     * @return void
793
     * @see doSetBitBin
794
     * @see doSetBitInt
795
     * @see doUnsetBin
796
     * @see doUnsetInt
797
     */
798 4
    public function setBit($ordinal, $bit)
799
    {
800 4
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
801 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...
802
        }
803
804 2
        if ($bit) {
805 2
            $this->{$this->fnDoSetBit}($ordinal);
806 1
        } else {
807 2
            $this->{$this->fnDoUnsetBit}($ordinal);
808
        }
809 2
    }
810
811
    /**
812
     * Set a bit at the given ordinal number.
813
     *
814
     * This is the binary bitset implementation.
815
     * 
816
     * @param int $ordinal Ordnal number of bit to set
817
     * @return void
818
     * @see setBit
819
     * @see doSetBitInt
820
     */
821 14
    private function doSetBitBin($ordinal)
822
    {
823 14
        $byte = (int) ($ordinal / 8);
824 14
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
825 14
    }
826
827
    /**
828
     * Set a bit at the given ordinal number.
829
     *
830
     * This is the binary bitset implementation.
831
     *
832
     * @param int $ordinal Ordnal number of bit to set
833
     * @return void
834
     * @see setBit
835
     * @see doSetBitBin
836
     */
837 70
    private function doSetBitInt($ordinal)
838
    {
839 70
        $this->bitset = $this->bitset | (1 << $ordinal);
840 70
    }
841
842
    /**
843
     * Unset a bit at the given ordinal number.
844
     *
845
     * This is the binary bitset implementation.
846
     *
847
     * @param int $ordinal Ordinal number of bit to unset
848
     * @return void
849
     * @see setBit
850
     * @see doUnsetBitInt
851
     */
852 6
    private function doUnsetBitBin($ordinal)
853
    {
854 6
        $byte = (int) ($ordinal / 8);
855 6
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
856 6
    }
857
858
    /**
859
     * Unset a bit at the given ordinal number.
860
     *
861
     * This is the integer bitset implementation.
862
     *
863
     * @param int $ordinal Ordinal number of bit to unset
864
     * @return void
865
     * @see setBit
866
     * @see doUnsetBitBin
867
     */
868 10
    private function doUnsetBitInt($ordinal)
869
    {
870 10
        $this->bitset = $this->bitset & ~(1 << $ordinal);
871 10
    }
872
}
873