Completed
Push — feature/74 ( 77e5ec...cd6a24 )
by Marc
02:02
created

EnumSet::doCountInt()   F

Complexity

Conditions 13
Paths 1030

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 34
ccs 26
cts 26
cp 1
rs 2.7716
cc 13
eloc 23
nc 1030
nop 0
crap 13

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 16
        for ($i = 0; $i < \PHP_INT_SIZE; ++$i) {
307 16
            $bitPos = $i * 8;
308 16
            $bitChk = 0xff << $bitPos;;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
309 16
            $byte = $bitset & $bitChk;
310 16
            if ($byte) {
311 14
                $byte = $byte >> $bitPos;
312 14
                if ($byte & 0b00000001) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
313 14
                if ($byte & 0b00000010) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
314 14
                if ($byte & 0b00000100) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
315 14
                if ($byte & 0b00001000) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
316 14
                if ($byte & 0b00010000) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
317 14
                if ($byte & 0b00100000) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
318 14
                if ($byte & 0b01000000) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
319 14
                if ($byte & 0b10000000) $count += 1;
0 ignored issues
show
Coding Style introduced by
Increment operators should be used where possible; found "$count += 1;" but expected "$count++"
Loading history...
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
     * 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...
373
     *
374
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
375
     * @return EnumSet
376
     */
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...
377 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...
378
    {
379 4
        $bitset = $this->bitset;
380 4
        foreach ($others as $other) {
381 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
382 2
                throw new InvalidArgumentException(sprintf(
383 2
                    'Others should be an instance of %s of the same enumeration as this %s',
384 2
                    __CLASS__,
385 2
                    $this->enumeration
386 1
                ));
387
            }
388
389 2
            $bitset |= $other->bitset;
390 1
        }
391
392 2
        $clone = clone $this;
393 2
        $clone->bitset = $bitset;
394 2
        return $clone;
395
    }
396
397
    /**
398
     * Produce a new set with enumerators common to both this and other (this & other)
399
     *
400
     * 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...
401
     *
402
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
403
     * @return EnumSet
404
     */
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...
405 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...
406
    {
407 4
        $bitset = $this->bitset;
408 4
        foreach ($others as $other) {
409 4
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
410 2
                throw new InvalidArgumentException(sprintf(
411 2
                    'Others should be an instance of %s of the same enumeration as this %s',
412 2
                    __CLASS__,
413 2
                    $this->enumeration
414 1
                ));
415
            }
416
417 2
            $bitset &= $other->bitset;
418 1
        }
419
420 2
        $clone = clone $this;
421 2
        $clone->bitset = $bitset;
422 2
        return $clone;
423
    }
424
425
    /**
426
     * Produce a new set with enumerators in this but not in other (this - other)
427
     *
428
     * 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...
429
     *
430
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
431
     * @return EnumSet
432
     */
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...
433 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...
434
    {
435 4
        $clone = clone $this;
436
437 4
        if (isset($others[0])) {
438 4
            $bitset = $others[0]->bitset;
439 4
            foreach ($others as $other) {
440 4
                if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
441 2
                    throw new InvalidArgumentException(sprintf(
442 2
                        'Others should of the same enumeration as this %s',
443 2
                        __CLASS__,
444 2
                        $this->enumeration
445 1
                    ));
446
                }
447
448 2
                $bitset |= $other->bitset;
449 1
            }
450
451 2
            $clone->bitset = $this->bitset & ~$bitset;
452 1
        }
453
454 2
        return $clone;
455
    }
456
457
    /**
458
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
459
     *
460
     * 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...
461
     *
462
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
463
     * @return EnumSet
464
     */
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...
465 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...
466
    {
467 4
        $clone = clone $this;
468
469 4
        if (isset($others[0])) {
470 4
            $bitset = $others[0]->bitset;
471 4
            foreach ($others as $other) {
472 4
                if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
473 2
                    throw new InvalidArgumentException(sprintf(
474 2
                        'Others should be an instance of %s of the same enumeration as this %s',
475 2
                        __CLASS__,
476 2
                        $this->enumeration
477 1
                    ));
478
                }
479
480 2
                $bitset |= $other->bitset;
481 1
            }
482
483 2
            $clone->bitset = $this->bitset ^ $bitset;
484 1
        }
485
486 2
        return $clone;
487
    }
488
489
    /**
490
     * Get ordinal numbers of the defined enumerators as array
491
     * @return int[]
492
     */
493 28
    public function getOrdinals()
494
    {
495 28
        return $this->{$this->fnDoGetOrdinals}();
496
    }
497
498
    /**
499
     * Get ordinal numbers of the defined enumerators as array.
500
     *
501
     * This is the binary bitset implementation.
502
     *
503
     * @return int[]
504
     * @see getOrdinals
505
     * @see goGetOrdinalsInt
506
     */
507 4
    private function doGetOrdinalsBin()
508
    {
509 4
        $ordinals = [];
510 4
        $byteLen = strlen($this->bitset);
511 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
512 4
            if ($this->bitset[$bytePos] === "\0") {
513
                // fast skip null byte
514 4
                continue;
515
            }
516
517 4
            $ord = ord($this->bitset[$bytePos]);
518 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
519 4
                if ($ord & (1 << $bitPos)) {
520 4
                    $ordinals[] = $bytePos * 8 + $bitPos;
521 2
                }
522 2
            }
523 2
        }
524 4
        return $ordinals;
525
    }
526
527
    /**
528
     * Get ordinal numbers of the defined enumerators as array.
529
     *
530
     * This is the integer bitset implementation.
531
     *
532
     * @return int[]
533
     * @see getOrdinals
534
     * @see doGetOrdinalsBin
535
     */
536 24
    private function doGetOrdinalsInt()
537
    {
538 24
        $ordinals = [];
539 24
        for ($ord = 0; $ord < $this->ordinalMax; ++$ord) {
540 24
            if ($this->bitset & (1 << $ord)) {
541 24
                $ordinals[] = $ord;
542 12
            }
543 12
        }
544 24
        return $ordinals;
545
    }
546
547
    /**
548
     * Get values of the defined enumerators as array
549
     * @return null[]|bool[]|int[]|float[]|string[]
550
     */
551 12
    public function getValues()
552
    {
553 12
        $enumeration = $this->enumeration;
554 12
        $values      = [];
555 12
        foreach ($this->getOrdinals() as $ord) {
556 12
            $values[] = $enumeration::byOrdinal($ord)->getValue();
557 6
        }
558 12
        return $values;
559
    }
560
561
    /**
562
     * Get names of the defined enumerators as array
563
     * @return string[]
564
     */
565 4
    public function getNames()
566
    {
567 4
        $enumeration = $this->enumeration;
568 4
        $names       = [];
569 4
        foreach ($this->getOrdinals() as $ord) {
570 4
            $names[] = $enumeration::byOrdinal($ord)->getName();
571 2
        }
572 4
        return $names;
573
    }
574
575
    /**
576
     * Get the defined enumerators as array
577
     * @return Enum[]
578
     */
579 4
    public function getEnumerators()
580
    {
581 4
        $enumeration = $this->enumeration;
582 4
        $enumerators = [];
583 4
        foreach ($this->getOrdinals() as $ord) {
584 4
            $enumerators[] = $enumeration::byOrdinal($ord);
585 2
        }
586 4
        return $enumerators;
587
    }
588
589
    /**
590
     * Get binary bitset in little-endian order
591
     * 
592
     * @return string
593
     */
594 12
    public function getBinaryBitsetLe()
595
    {
596 12
        return $this->{$this->fnDoGetBinaryBitsetLe}();
597
    }
598
599
    /**
600
     * Get binary bitset in little-endian order.
601
     *
602
     * This is the binary bitset implementation.
603
     *
604
     * @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...
605
     */
606 8
    private function doGetBinaryBitsetLeBin()
607
    {
608 8
        return $this->bitset;
609
    }
610
611
    /**
612
     * Get binary bitset in little-endian order.
613
     *
614
     * This is the integer bitset implementation.
615
     *
616
     * @return string
617
     */
618 4
    private function doGetBinaryBitsetLeInt()
619
    {
620 4
        $bin = pack(PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
621 4
        return substr($bin, 0, ceil($this->ordinalMax / 8));
622
    }
623
624
    /**
625
     * Set binary bitset in little-endian order
626
     *
627
     * NOTE: It resets the current position of the iterator
628
     * 
629
     * @param string $bitset
630
     * @return void
631
     * @throws InvalidArgumentException On a non string is given as Parameter
632
     */
633 26
    public function setBinaryBitsetLe($bitset)
634
    {
635 26
        if (!is_string($bitset)) {
636 2
            throw new InvalidArgumentException('Bitset must be a string');
637
        }
638
639 24
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
640
641
        // reset the iterator position
642 12
        $this->rewind();
643 12
    }
644
645
    /**
646
     * Set binary bitset in little-endian order
647
     *
648
     * NOTE: It resets the current position of the iterator
649
     *
650
     * @param string $bitset
651
     * @return void
652
     * @throws InvalidArgumentException On a non string is given as Parameter
653
     */
654 14
    private function doSetBinaryBitsetLeBin($bitset)
655
    {
656 14
        $size   = strlen($this->bitset);
657 14
        $sizeIn = strlen($bitset);
658
659 14
        if ($sizeIn < $size) {
660
            // add "\0" if the given bitset is not long enough
661 2
            $bitset .= str_repeat("\0", $size - $sizeIn);
662 13
        } elseif ($sizeIn > $size) {
663 4
            if (trim(substr($bitset, $size), "\0") !== '') {
664 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
665
            }
666 2
            $bitset = substr($bitset, 0, $size);
667 1
        }
668
669
        // truncate out-of-range bits of last byte
670 12
        $lastByteMaxOrd = $this->ordinalMax % 8;
671 12
        if ($lastByteMaxOrd !== 0) {
672 12
            $lastByte         = $bitset[$size - 1];
673 12
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
674 12
            if ($lastByte !== $lastByteExpected) {
675 4
                throw new InvalidArgumentException('Out-Of-Range bits detected');
676
            }
677
678 8
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
679 4
        }
680
681 8
        $this->bitset = $bitset;
682 8
    }
683
684
    /**
685
     * Set binary bitset in little-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 10
    private function doSetBinaryBitsetLeInt($bitset)
694
    {
695 10
        $len = strlen($bitset);
696 10
        $int = 0;
697 10
        for ($i = 0; $i < $len; ++$i) {
698 10
            $ord = ord($bitset[$i]);
699
700 10
            if ($ord && $i > PHP_INT_SIZE - 1) {
701 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
702
            }
703
704 10
            $int |= $ord << (8 * $i);
705 5
        }
706
707 8
        if ($int & (~0 << $this->ordinalMax)) {
708 4
            throw new InvalidArgumentException('Out-Of-Range bits detected');
709
        }
710
711 4
        $this->bitset = $int;
712 4
    }
713
714
    /**
715
     * Get binary bitset in big-endian order
716
     * 
717
     * @return string
718
     */
719 2
    public function getBinaryBitsetBe()
720
    {
721 2
        return strrev($this->bitset);
722
    }
723
724
    /**
725
     * Set binary bitset in big-endian order
726
     *
727
     * NOTE: It resets the current position of the iterator
728
     * 
729
     * @param string $bitset
730
     * @return void
731
     * @throws InvalidArgumentException On a non string is given as Parameter
732
     */
733 4
    public function setBinaryBitsetBe($bitset)
734
    {
735 4
        if (!is_string($bitset)) {
736 2
            throw new InvalidArgumentException('Bitset must be a string');
737
        }
738 2
        $this->setBinaryBitsetLe(strrev($bitset));
739 2
    }
740
741
    /**
742
     * Get a bit at the given ordinal number
743
     *
744
     * @param int $ordinal Ordinal number of bit to get
745
     * @return boolean
746
     */
747 6
    public function getBit($ordinal)
748
    {
749 6
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
750 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...
751
        }
752
753 4
        return $this->{$this->fnDoGetBit}($ordinal);
754
    }
755
756
    /**
757
     * Get a bit at the given ordinal number.
758
     *
759
     * This is the binary bitset implementation.
760
     *
761
     * @param int $ordinal Ordinal number of bit to get
762
     * @return boolean
763
     * @see getBit
764
     * @see doGetBitInt
765
     */
766 18
    private function doGetBitBin($ordinal)
767
    {
768 18
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
769
    }
770
771
    /**
772
     * Get a bit at the given ordinal number.
773
     *
774
     * This is the integer bitset implementation.
775
     * 
776
     * @param int $ordinal Ordinal number of bit to get
777
     * @return boolean
778
     * @see getBit
779
     * @see doGetBitBin
780
     */
781 32
    private function doGetBitInt($ordinal)
782
    {
783 32
        return (bool)($this->bitset & (1 << $ordinal));
784
    }
785
786
    /**
787
     * Set a bit at the given ordinal number
788
     *
789
     * @param int $ordinal Ordnal number of bit to set
790
     * @param bool $bit    The bit to set
791
     * @return void
792
     * @see doSetBitBin
793
     * @see doSetBitInt
794
     * @see doUnsetBin
795
     * @see doUnsetInt
796
     */
797 4
    public function setBit($ordinal, $bit)
798
    {
799 4
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
800 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...
801
        }
802
803 2
        if ($bit) {
804 2
            $this->{$this->fnDoSetBit}($ordinal);
805 1
        } else {
806 2
            $this->{$this->fnDoUnsetBit}($ordinal);
807
        }
808 2
    }
809
810
    /**
811
     * Set a bit at the given ordinal number.
812
     *
813
     * This is the binary bitset implementation.
814
     * 
815
     * @param int $ordinal Ordnal number of bit to set
816
     * @return void
817
     * @see setBit
818
     * @see doSetBitInt
819
     */
820 14
    private function doSetBitBin($ordinal)
821
    {
822 14
        $byte = (int) ($ordinal / 8);
823 14
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
824 14
    }
825
826
    /**
827
     * Set a bit at the given ordinal number.
828
     *
829
     * This is the binary bitset implementation.
830
     *
831
     * @param int $ordinal Ordnal number of bit to set
832
     * @return void
833
     * @see setBit
834
     * @see doSetBitBin
835
     */
836 70
    private function doSetBitInt($ordinal)
837
    {
838 70
        $this->bitset = $this->bitset | (1 << $ordinal);
839 70
    }
840
841
    /**
842
     * Unset a bit at the given ordinal number.
843
     *
844
     * This is the binary bitset implementation.
845
     *
846
     * @param int $ordinal Ordinal number of bit to unset
847
     * @return void
848
     * @see setBit
849
     * @see doUnsetBitInt
850
     */
851 6
    private function doUnsetBitBin($ordinal)
852
    {
853 6
        $byte = (int) ($ordinal / 8);
854 6
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
855 6
    }
856
857
    /**
858
     * Unset a bit at the given ordinal number.
859
     *
860
     * This is the integer bitset implementation.
861
     *
862
     * @param int $ordinal Ordinal number of bit to unset
863
     * @return void
864
     * @see setBit
865
     * @see doUnsetBitBin
866
     */
867 10
    private function doUnsetBitInt($ordinal)
868
    {
869 10
        $this->bitset = $this->bitset & ~(1 << $ordinal);
870 10
    }
871
}
872