Passed
Push — 4.x ( 904590...375e58 )
by Marc
03:59
created

EnumSet::symDiff()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MabeEnum;
6
7
use Countable;
8
use InvalidArgumentException;
9
use Iterator;
10
use IteratorAggregate;
11
12
/**
13
 * A set of enumerators of the given enumeration (EnumSet<T extends Enum>)
14
 * based on an integer or binary bitset depending of given enumeration size.
15
 *
16
 * @copyright 2019 Marc Bennewitz
17
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
18
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
19
 */
3 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
20
class EnumSet implements IteratorAggregate, Countable
21
{
22
    /**
23
     * The classname of the Enumeration
24
     * @var string
25
     */
26
    private $enumeration;
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "enumeration" must contain a leading underscore
Loading history...
27
28
    /**
29
     * Number of enumerators defined in the enumeration
30
     * @var int
31
     */
32
    private $enumerationCount;
1 ignored issue
show
Coding Style introduced by
Private member variable "enumerationCount" must contain a leading underscore
Loading history...
33
34
    /**
35
     * Integer or binary (little endian) bitset
36
     * @var int|string
37
     */
38
    private $bitset = 0;
1 ignored issue
show
Coding Style introduced by
Private member variable "bitset" must contain a leading underscore
Loading history...
39
40
    /**#@+
41
     * Defines private method names to be called depended of how the bitset type was set too.
42
     * ... Integer or binary bitset.
43
     * ... *Int or *Bin method
44
     * 
45
     * @var string
46
     */
47
    private $fnDoGetIterator       = 'doGetIteratorInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoGetIterator" must contain a leading underscore
Loading history...
48
    private $fnDoCount             = 'doCountInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoCount" must contain a leading underscore
Loading history...
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
49
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoGetOrdinals" must contain a leading underscore
Loading history...
50
    private $fnDoGetBit            = 'doGetBitInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoGetBit" must contain a leading underscore
Loading history...
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
51
    private $fnDoSetBit            = 'doSetBitInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoSetBit" must contain a leading underscore
Loading history...
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
52
    private $fnDoUnsetBit          = 'doUnsetBitInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoUnsetBit" must contain a leading underscore
Loading history...
53
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoGetBinaryBitsetLe" must contain a leading underscore
Loading history...
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
54
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoSetBinaryBitsetLe" must contain a leading underscore
Loading history...
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
55
    /**#@-*/
56
57
    /**
58
     * Constructor
59
     *
60
     * @param string $enumeration The classname of the enumeration
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
Coding Style introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
61
     * @param iterable|null $enumerators iterable list of enumerators initializing the set
1 ignored issue
show
introduced by
Parameter comment must start with a capital letter
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
62
     * @throws InvalidArgumentException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
63
     */
64 99
    public function __construct(string $enumeration, iterable $enumerators = null)
2 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$enumerators" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$enumerators"; expected 0 but found 1
Loading history...
65
    {
66 99
        if (!\is_subclass_of($enumeration, Enum::class)) {
67 1
            throw new InvalidArgumentException(\sprintf(
68 1
                '%s can handle subclasses of %s only',
69 1
                __METHOD__,
70 1
                Enum::class
71
            ));
72
        }
73
74 98
        $this->enumeration      = $enumeration;
75 98
        $this->enumerationCount = \count($enumeration::getConstants());
76
77
        // By default the bitset is initialized as integer bitset
78
        // in case the enumeration has more enumerators then integer bits
79
        // we will switch this into a binary bitset
80 98
        if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
81
            // init binary bitset with zeros
82 23
            $this->bitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));
83
84
            // switch internal binary bitset functions
85 23
            $this->fnDoGetIterator       = 'doGetIteratorBin';
86 23
            $this->fnDoCount             = 'doCountBin';
87 23
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
88 23
            $this->fnDoGetBit            = 'doGetBitBin';
89 23
            $this->fnDoSetBit            = 'doSetBitBin';
90 23
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
91 23
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
92 23
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
93
        }
94
95 98
        if ($enumerators !== null) {
96 18
            foreach ($enumerators as $enumerator) {
97 18
                $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
98
            }
99
        }
100 98
    }
101
102
    /**
103
     * Get the classname of the enumeration
104
     * @return string
105
     */
106 2
    public function getEnumeration(): string
107
    {
108 2
        return $this->enumeration;
109
    }
110
111
    /* write access (mutable) */
112
113
    /**
114
     * Adds an enumerator object or value
115
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
116
     * @return void
117
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
118
     */
119 16
    public function add($enumerator): void
120
    {
121 16
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
122 16
    }
123
124
    /**
125
     * Adds all enumerator objects or values of the given iterable
126
     * @param iterable $enumerators Iterable list of enumerator objects or values
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
127
     * @return void
128
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
129
     */
130 6
    public function addIterable(iterable $enumerators): void
131
    {
132 6
        $bitset = $this->bitset;
133
134
        try {
135 6
            foreach ($enumerators as $enumerator) {
136 6
                $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
137
            }
138 1
        } catch (\Throwable $e) {
139
            // reset all changes until error happened
140 1
            $this->bitset = $bitset;
141 1
            throw $e;
142
        }
143 5
    }
144
145
    /**
146
     * Removes the given enumerator object or value
147
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
148
     * @return void
149
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
150
     */
151 8
    public function remove($enumerator): void
152
    {
153 8
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
154 8
    }
155
156
    /**
157
     * Adds an enumerator object or value
158
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
159
     * @return void
160
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
161
     * @see add()
162
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
163
     */
164 1
    public function attach($enumerator): void
165
    {
166 1
        $this->add($enumerator);
167 1
    }
168
169
    /**
170
     * Removes the given enumerator object or value
171
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
172
     * @return void
173
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
174
     * @see remove()
175
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
176
     */
177 1
    public function detach($enumerator): void
178
    {
179 1
        $this->remove($enumerator);
180 1
    }
181
182
    /**
183
     * Removes all enumerator objects or values of the given iterable
184
     * @param iterable $enumerators Iterable list of enumerator objects or values
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
185
     * @return void
186
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
187
     */
188 6
    public function removeIterable(iterable $enumerators): void
189
    {
190 6
        $bitset = $this->bitset;
191
192
        try {
193 6
            foreach ($enumerators as $enumerator) {
194 6
                $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
195
            }
196 1
        } catch (\Throwable $e) {
197
            // reset all changes until error happened
198 1
            $this->bitset = $bitset;
199 1
            throw $e;
200
        }
201 5
    }
202
203
    /**
204
     * Modify this set from both this and other (this | other)
205
     *
206
     * @param EnumSet $other EnumSet of the same enumeration to produce the union
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
207
     * @return void
208
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
209
     */
210 4
    public function setUnion(EnumSet $other): void
211
    {
212 4
        if ($this->enumeration !== $other->enumeration) {
213 1
            throw new InvalidArgumentException(\sprintf(
214 1
                'Other should be of the same enumeration as this %s',
215 1
                $this->enumeration
216
            ));
217
        }
218
219 3
        $this->bitset = $this->bitset | $other->bitset;
220 3
    }
221
222
    /**
223
     * Modify this set with enumerators common to both this and other (this & other)
224
     *
225
     * @param EnumSet $other EnumSet of the same enumeration to produce the intersect
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
226
     * @return void
227
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
228
     */
229 4
    public function setIntersect(EnumSet $other): void
230
    {
231 4
        if ($this->enumeration !== $other->enumeration) {
232 1
            throw new InvalidArgumentException(\sprintf(
233 1
                'Other should be of the same enumeration as this %s',
234 1
                $this->enumeration
235
            ));
236
        }
237
238 3
        $this->bitset = $this->bitset & $other->bitset;
239 3
    }
240
241
    /**
242
     * Modify this set with enumerators in this but not in other (this - other)
243
     *
244
     * @param EnumSet $other EnumSet of the same enumeration to produce the diff
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
245
     * @return void
246
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
247
     */
248 4
    public function setDiff(EnumSet $other): void
249
    {
250 4
        if ($this->enumeration !== $other->enumeration) {
251 1
            throw new InvalidArgumentException(\sprintf(
252 1
                'Other should be of the same enumeration as this %s',
253 1
                $this->enumeration
254
            ));
255
        }
256
257 3
        $this->bitset = $this->bitset & ~$other->bitset;
258 3
    }
259
260
    /**
261
     * Modify this set with enumerators in either this and other but not in both (this ^ other)
262
     *
263
     * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
264
     * @return void
265
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
266
     */
267 4
    public function setSymDiff(EnumSet $other): void
268
    {
269 4
        if ($this->enumeration !== $other->enumeration) {
270 1
            throw new InvalidArgumentException(\sprintf(
271 1
                'Other should be of the same enumeration as this %s',
272 1
                $this->enumeration
273
            ));
274
        }
275
276 3
        $this->bitset = $this->bitset ^ $other->bitset;
277 3
    }
278
279
    /**
280
     * Set the given binary bitset in little-endian order
281
     *
282
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
283
     * @return void
284
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
285
     * @uses doSetBinaryBitsetLeBin()
286
     * @uses doSetBinaryBitsetLeInt()
287
     */
288 1
    public function setBinaryBitsetLe(string $bitset): void
289
    {
290 1
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
291 1
    }
292
293
    /**
294
     * Set binary bitset in little-endian order
295
     *
296
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
297
     * @return void
298
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
299
     * @see setBinaryBitsetLeBin()
300
     * @see doSetBinaryBitsetLeInt()
301
     */
302 9
    private function doSetBinaryBitsetLeBin($bitset): void
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $bitset
Loading history...
303
    {
304 9
        $size   = \strlen($this->bitset);
305 9
        $sizeIn = \strlen($bitset);
306
307 9
        if ($sizeIn < $size) {
308
            // add "\0" if the given bitset is not long enough
309 1
            $bitset .= \str_repeat("\0", $size - $sizeIn);
310 8
        } elseif ($sizeIn > $size) {
311 2
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
312 1
                throw new InvalidArgumentException('out-of-range bits detected');
313
            }
314 1
            $bitset = \substr($bitset, 0, $size);
315
        }
316
317
        // truncate out-of-range bits of last byte
318 8
        $lastByteMaxOrd = $this->enumerationCount % 8;
319 8
        if ($lastByteMaxOrd !== 0) {
320 8
            $lastByte         = $bitset[-1];
321 8
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
322 8
            if ($lastByte !== $lastByteExpected) {
323 2
                throw new InvalidArgumentException('out-of-range bits detected');
324
            }
325
326 6
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
327
        }
328
329 6
        $this->bitset = $bitset;
330 6
    }
331
332
    /**
333
     * Set binary bitset in little-endian order
334
     *
335
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
336
     * @return void
337
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
338
     * @see setBinaryBitsetLeBin()
339
     * @see doSetBinaryBitsetLeBin()
340
     */
341 5
    private function doSetBinaryBitsetLeInt($bitset): void
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $bitset
Loading history...
342
    {
343 5
        $len = \strlen($bitset);
344 5
        $int = 0;
345 5
        for ($i = 0; $i < $len; ++$i) {
346 5
            $ord = \ord($bitset[$i]);
347
348 5
            if ($ord && $i > \PHP_INT_SIZE - 1) {
349 1
                throw new InvalidArgumentException('out-of-range bits detected');
350
            }
351
352 5
            $int |= $ord << (8 * $i);
353
        }
354
355 4
        if ($int & (~0 << $this->enumerationCount)) {
356 2
            throw new InvalidArgumentException('out-of-range bits detected');
357
        }
358
359 2
        $this->bitset = $int;
360 2
    }
361
362
    /**
363
     * Set the given binary bitset in big-endian order
364
     *
365
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
366
     * @return void
367
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
368
     */
369 1
    public function setBinaryBitsetBe(string $bitset): void
370
    {
371 1
        $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
372 1
    }
373
374
    /**
375
     * Set a bit at the given ordinal number
376
     *
377
     * @param int $ordinal Ordinal number of bit to set
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
378
     * @param bool $bit    The bit to set
2 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter name; 4 found
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
379
     * @return void
380
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
381
     * @uses doSetBitBin()
382
     * @uses doSetBitInt()
383
     * @uses doUnsetBitBin()
384
     * @uses doUnsetBitInt()
385
     */
386 3
    public function setBit(int $ordinal, bool $bit): void
387
    {
388 3
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
389 1
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
1 ignored issue
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...
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 113 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
390
        }
391
392 2
        if ($bit) {
393 2
            $this->{$this->fnDoSetBit}($ordinal);
394
        } else {
395 2
            $this->{$this->fnDoUnsetBit}($ordinal);
396
        }
397 2
    }
398
399
    /**
400
     * Set a bit at the given ordinal number.
401
     *
402
     * This is the binary bitset implementation.
403
     *
404
     * @param int $ordinal Ordinal number of bit to set
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
405
     * @return void
406
     * @see setBit()
407
     * @see doSetBitInt()
408
     */
409 14
    private function doSetBitBin($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
410
    {
411 14
        $byte = (int) ($ordinal / 8);
412 14
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
413 14
    }
414
415
    /**
416
     * Set a bit at the given ordinal number.
417
     *
418
     * This is the binary bitset implementation.
419
     *
420
     * @param int $ordinal Ordinal number of bit to set
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
421
     * @return void
422
     * @see setBit()
423
     * @see doSetBitBin()
424
     */
425 60
    private function doSetBitInt($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
426
    {
427 60
        $this->bitset = $this->bitset | (1 << $ordinal);
428 60
    }
429
430
    /**
431
     * Unset a bit at the given ordinal number.
432
     *
433
     * This is the binary bitset implementation.
434
     *
435
     * @param int $ordinal Ordinal number of bit to unset
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
436
     * @return void
437
     * @see setBit()
438
     * @see doUnsetBitInt()
439
     */
440 10
    private function doUnsetBitBin($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
441
    {
442 10
        $byte = (int) ($ordinal / 8);
443 10
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
444 10
    }
445
446
    /**
447
     * Unset a bit at the given ordinal number.
448
     *
449
     * This is the integer bitset implementation.
450
     *
451
     * @param int $ordinal Ordinal number of bit to unset
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
452
     * @return void
453
     * @see setBit()
454
     * @see doUnsetBitBin()
455
     */
456 20
    private function doUnsetBitInt($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
457
    {
458 20
        $this->bitset = $this->bitset & ~(1 << $ordinal);
459 20
    }
460
461
    /* write access (immutable) */
462
463
    /**
464
     * Creates a new set with the given enumerator object or value added
465
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
466
     * @return static
467
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
468
     */
469 28
    public function with($enumerator): self
470
    {
471 28
        $clone = clone $this;
472 28
        $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
473 28
        return $clone;
474
    }
475
476
    /**
477
     * Creates a new set with the given enumeration objects or values added
478
     * @param iterable $enumerators Iterable list of enumerator objects or values
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
479
     * @return static
480
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
481
     */
482 5
    public function withIterable(iterable $enumerators): self
483
    {
484 5
        $clone = clone $this;
485 5
        foreach ($enumerators as $enumerator) {
486 5
            $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
487
        }
488 5
        return $clone;
489
    }
490
491
    /**
492
     * Create a new set with the given enumerator object or value removed
493
     * @param Enum|null|bool|int|float|string|array $enumerator Enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
494
     * @return static
495
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
496
     */
497 9
    public function without($enumerator): self
498
    {
499 9
        $clone = clone $this;
500 9
        $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
501 9
        return $clone;
502
    }
503
504
    /**
505
     * Creates a new set with the given enumeration objects or values removed
506
     * @param iterable $enumerators Iterable list of enumerator objects or values
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
507
     * @return static
508
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
509
     */
510 5
    public function withoutIterable(iterable $enumerators): self
511
    {
512 5
        $clone = clone $this;
513 5
        foreach ($enumerators as $enumerator) {
514 5
            $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
515
        }
516 5
        return $clone;
517
    }
518
519
    /**
520
     * Create a new set with enumerators from both this and other (this | other)
521
     *
522
     * @param EnumSet $other EnumSet of the same enumeration to produce the union
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
523
     * @return static
524
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
525
     */
526 2
    public function withUnion(EnumSet $other): self
527
    {
528 2
        $clone = clone $this;
529 2
        $clone->setUnion($other);
530 2
        return $clone;
531
    }
532
533
    /**
534
     * Create a new set with enumerators from both this and other (this | other)
535
     *
536
     * @param EnumSet $other EnumSet of the same enumeration to produce the union
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
537
     * @return static
538
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
539
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
540
     */
541 1
    public function union(EnumSet $other): self
542
    {
543 1
        return $this->withUnion($other);
544
    }
545
546
    /**
547
     * Create a new set with enumerators common to both this and other (this & other)
548
     *
549
     * @param EnumSet $other EnumSet of the same enumeration to produce the intersect
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
550
     * @return static
551
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
552
     */
553 2
    public function withIntersect(EnumSet $other): self
554
    {
555 2
        $clone = clone $this;
556 2
        $clone->setIntersect($other);
557 2
        return $clone;
558
    }
559
560
    /**
561
     * Create a new set with enumerators common to both this and other (this & other)
562
     *
563
     * @param EnumSet $other EnumSet of the same enumeration to produce the intersect
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
564
     * @return static
565
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
566
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
567
     */
568 1
    public function intersect(EnumSet $other): self
569
    {
570 1
        return $this->withIntersect($other);
571
    }
572
573
    /**
574
     * Create a new set with enumerators in this but not in other (this - other)
575
     *
576
     * @param EnumSet $other EnumSet of the same enumeration to produce the diff
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
577
     * @return static
578
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
579
     */
580 2
    public function withDiff(EnumSet $other): self
581
    {
582 2
        $clone = clone $this;
583 2
        $clone->setDiff($other);
584 2
        return $clone;
585
    }
586
587
    /**
588
     * Create a new set with enumerators in this but not in other (this - other)
589
     *
590
     * @param EnumSet $other EnumSet of the same enumeration to produce the diff
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
591
     * @return static
592
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
593
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
594
     */
595 1
    public function diff(EnumSet $other): self
596
    {
597 1
        return $this->withDiff($other);
598
    }
599
600
    /**
601
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
602
     *
603
     * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
604
     * @return static
605
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
606
     */
607 2
    public function withSymDiff(EnumSet $other): self
608
    {
609 2
        $clone = clone $this;
610 2
        $clone->setSymDiff($other);
611 2
        return $clone;
612
    }
613
614
    /**
615
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
616
     *
617
     * @param EnumSet $other EnumSet of the same enumeration to produce the symmetric difference
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
618
     * @return static
619
     * @throws InvalidArgumentException If $other doesn't match the enumeration
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
620
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
621
     */
622 1
    public function symDiff(EnumSet $other): self
623
    {
624 1
        return $this->withSymDiff($other);
625
    }
626
627
    /**
628
     * Create a new set with the given binary bitset in little-endian order
629
     *
630
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
631
     * @return static
632
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
633
     * @uses doSetBinaryBitsetLeBin()
634
     * @uses doSetBinaryBitsetLeInt()
635
     */
636 11
    public function withBinaryBitsetLe(string $bitset): self
637
    {
638 11
        $clone = clone $this;
639 11
        $clone->{$this->fnDoSetBinaryBitsetLe}($bitset);
640 5
        return $clone;
641
    }
642
643
    /**
644
     * Create a new set with the given binary bitset in big-endian order
645
     *
646
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
647
     * @return static
648
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
649
     */
650 1
    public function withBinaryBitsetBe(string $bitset): self
651
    {
652 1
        $clone = $this;
653 1
        $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
654 1
        return $clone;
655
    }
656
657
    /**
658
     * Create a new set with the bit at the given ordinal number set
659
     *
660
     * @param int $ordinal Ordinal number of bit to set
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
661
     * @param bool $bit    The bit to set
2 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter name; 4 found
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
662
     * @return static
663
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
664
     * @uses doSetBitBin()
665
     * @uses doSetBitInt()
666
     * @uses doUnsetBitBin()
667
     * @uses doUnsetBitInt()
668
     */
669 1
    public function withBit(int $ordinal, bool $bit): self
670
    {
671 1
        $clone = clone $this;
672 1
        $clone->setBit($ordinal, $bit);
673 1
        return $clone;
674
    }
675
676
    /* read access */
677
678
    /**
679
     * Test if the given enumerator exists
680
     * @param Enum|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
681
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
682
     */
683 24
    public function has($enumerator): bool
684
    {
685 24
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
686
    }
687
688
    /**
689
     * Test if the given enumerator exists
690
     * @param Enum|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
691
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
692
     * @see has()
693
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
694
     */
695 1
    public function contains($enumerator): bool
696
    {
697 1
        return $this->has($enumerator);
698
    }
699
700
    /* IteratorAggregate */
701
702
    /**
703
     * Get a new iterator
704
     * @return Iterator
705
     * @uses doGetIteratorInt()
706
     * @uses doGetIteratorBin()
707
     */
708 13
    public function getIterator(): Iterator
709
    {
710 13
        return $this->{$this->fnDoGetIterator}();
711
    }
712
713
    /**
714
     * Get a new Iterator.
715
     *
716
     * This is the binary bitset implementation.
717
     *
718
     * @return Iterator
719
     * @see getIterator()
720
     * @see goGetIteratorInt()
721
     */
722 4
    private function doGetIteratorBin()
723
    {
724 4
        $bitset   = $this->bitset;
725 4
        $byteLen  = \strlen($bitset);
726 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
727 4
            if ($bitset[$bytePos] === "\0") {
728
                // fast skip null byte
729 4
                continue;
730
            }
731
732 4
            $ord = \ord($bitset[$bytePos]);
733 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
734 4
                if ($ord & (1 << $bitPos)) {
735 4
                    $ordinal = $bytePos * 8 + $bitPos;
736 4
                    yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
737
                }
738
            }
739
        }
740 4
    }
741
742
    /**
743
     * Get a new Iterator.
744
     *
745
     * This is the integer bitset implementation.
746
     *
747
     * @return Iterator
748
     * @see getIterator()
749
     * @see doGetIteratorBin()
750
     */
751 9
    private function doGetIteratorInt()
752
    {
753 9
        $count  = $this->enumerationCount;
754 9
        $bitset = $this->bitset;
755 9
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
756 9
            if ($bitset & (1 << $ordinal)) {
757 7
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
758
            }
759
        }
760 7
    }
761
762
    /* Countable */
763
764
    /**
765
     * Count the number of elements
766
     *
767
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
768
     * @uses doCountBin()
769
     * @uses doCountInt()
770
     */
771 32
    public function count(): int
772
    {
773 32
        return $this->{$this->fnDoCount}();
774
    }
775
776
    /**
777
     * Count the number of elements.
778
     *
779
     * This is the binary bitset implementation.
780
     *
781
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
782
     * @see count()
783
     * @see doCountInt()
784
     */
785 15
    private function doCountBin()
786
    {
787 15
        $count   = 0;
788 15
        $bitset  = $this->bitset;
789 15
        $byteLen = \strlen($bitset);
790 15
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
791 15
            if ($bitset[$bytePos] === "\0") {
792
                // fast skip null byte
793 15
                continue;
794
            }
795
796 15
            $ord = \ord($bitset[$bytePos]);
797 15
            if ($ord & 0b00000001) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
798 15
            if ($ord & 0b00000010) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
799 15
            if ($ord & 0b00000100) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
800 15
            if ($ord & 0b00001000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
801 15
            if ($ord & 0b00010000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
802 15
            if ($ord & 0b00100000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
803 15
            if ($ord & 0b01000000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
804 15
            if ($ord & 0b10000000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
805
        }
806 15
        return $count;
807
    }
808
809
    /**
810
     * Count the number of elements.
811
     *
812
     * This is the integer bitset implementation.
813
     *
814
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
815
     * @see count()
816
     * @see doCountBin()
817
     */
818 17
    private function doCountInt()
819
    {
820 17
        $count  = 0;
821 17
        $bitset = $this->bitset;
822
823
        // PHP does not support right shift unsigned
824 17
        if ($bitset < 0) {
825 5
            $count  = 1;
826 5
            $bitset = $bitset & \PHP_INT_MAX;
827
        }
828
829
        // iterate byte by byte and count set bits
830 17
        $phpIntBitSize = \PHP_INT_SIZE * 8;
831 17
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
832 17
            $bitChk = 0xff << $bitPos;
833 17
            $byte = $bitset & $bitChk;
834 17
            if ($byte) {
835 16
                $byte = $byte >> $bitPos;
836 16
                if ($byte & 0b00000001) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
837 16
                if ($byte & 0b00000010) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
838 16
                if ($byte & 0b00000100) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
839 16
                if ($byte & 0b00001000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
840 16
                if ($byte & 0b00010000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
841 16
                if ($byte & 0b00100000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
842 16
                if ($byte & 0b01000000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
843 16
                if ($byte & 0b10000000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
844
            }
845
846 17
            if ($bitset <= $bitChk) {
847 17
                break;
848
            }
849
        }
850
851 17
        return $count;
852
    }
853
854
    /**
855
     * Check if this EnumSet is the same as other
856
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
857
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
858
     */
859 3
    public function isEqual(EnumSet $other): bool
860
    {
861 3
        return $this->enumeration === $other->enumeration
862 3
            && $this->bitset === $other->bitset;
863
    }
864
865
    /**
866
     * Check if this EnumSet is a subset of other
867
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
868
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
869
     */
870 4
    public function isSubset(EnumSet $other): bool
871
    {
872 4
        return $this->enumeration === $other->enumeration
873 4
            && ($this->bitset & $other->bitset) === $this->bitset;
874
    }
875
876
    /**
877
     * Check if this EnumSet is a superset of other
878
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
879
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
880
     */
881 4
    public function isSuperset(EnumSet $other): bool
882
    {
883 4
        return $this->enumeration === $other->enumeration
884 4
            && ($this->bitset | $other->bitset) === $this->bitset;
885
    }
886
887
    /**
888
     * Get ordinal numbers of the defined enumerators as array
889
     * @return int[]
890
     * @uses  doGetOrdinalsBin()
891
     * @uses  doGetOrdinalsInt()
892
     */
893 18
    public function getOrdinals(): array
894
    {
895 18
        return $this->{$this->fnDoGetOrdinals}();
896
    }
897
898
    /**
899
     * Get ordinal numbers of the defined enumerators as array.
900
     *
901
     * This is the binary bitset implementation.
902
     *
903
     * @return int[]
904
     * @see getOrdinals()
905
     * @see goGetOrdinalsInt()
906
     */
907 1
    private function doGetOrdinalsBin()
908
    {
909 1
        $ordinals = [];
910 1
        $bitset   = $this->bitset;
911 1
        $byteLen  = \strlen($bitset);
912 1
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
913 1
            if ($bitset[$bytePos] === "\0") {
914
                // fast skip null byte
915 1
                continue;
916
            }
917
918 1
            $ord = \ord($bitset[$bytePos]);
919 1
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
920 1
                if ($ord & (1 << $bitPos)) {
921 1
                    $ordinals[] = $bytePos * 8 + $bitPos;
922
                }
923
            }
924
        }
925 1
        return $ordinals;
926
    }
927
928
    /**
929
     * Get ordinal numbers of the defined enumerators as array.
930
     *
931
     * This is the integer bitset implementation.
932
     *
933
     * @return int[]
934
     * @see getOrdinals()
935
     * @see doGetOrdinalsBin()
936
     */
937 17
    private function doGetOrdinalsInt()
938
    {
939 17
        $ordinals = [];
940 17
        $count    = $this->enumerationCount;
941 17
        $bitset   = $this->bitset;
942 17
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
943 17
            if ($bitset & (1 << $ordinal)) {
944 17
                $ordinals[] = $ordinal;
945
            }
946
        }
947 17
        return $ordinals;
948
    }
949
950
    /**
951
     * Get values of the defined enumerators as array
952
     * @return mixed[]
953
     */
954 13
    public function getValues(): array
955
    {
956 13
        $enumeration = $this->enumeration;
957 13
        $values      = [];
958 13
        foreach ($this->getOrdinals() as $ord) {
959 13
            $values[] = $enumeration::byOrdinal($ord)->getValue();
960
        }
961 13
        return $values;
962
    }
963
964
    /**
965
     * Get names of the defined enumerators as array
966
     * @return string[]
967
     */
968 1
    public function getNames(): array
969
    {
970 1
        $enumeration = $this->enumeration;
971 1
        $names       = [];
972 1
        foreach ($this->getOrdinals() as $ord) {
973 1
            $names[] = $enumeration::byOrdinal($ord)->getName();
974
        }
975 1
        return $names;
976
    }
977
978
    /**
979
     * Get the defined enumerators as array
980
     * @return Enum[]
981
     */
982 2
    public function getEnumerators(): array
983
    {
984 2
        $enumeration = $this->enumeration;
985 2
        $enumerators = [];
986 2
        foreach ($this->getOrdinals() as $ord) {
987 2
            $enumerators[] = $enumeration::byOrdinal($ord);
988
        }
989 2
        return $enumerators;
990
    }
991
992
    /**
993
     * Get binary bitset in little-endian order
994
     * 
995
     * @return string
996
     * @uses doGetBinaryBitsetLeBin()
997
     * @uses doGetBinaryBitsetLeInt()
998
     */
999 6
    public function getBinaryBitsetLe(): string
1000
    {
1001 6
        return $this->{$this->fnDoGetBinaryBitsetLe}();
1002
    }
1003
1004
    /**
1005
     * Get binary bitset in little-endian order.
1006
     *
1007
     * This is the binary bitset implementation.
1008
     *
1009
     * @return string
1010
     * @see getBinaryBitsetLe()
1011
     * @see doGetBinaryBitsetLeInt()
1012
     */
1013 4
    private function doGetBinaryBitsetLeBin()
1014
    {
1015 4
        return $this->bitset;
1016
    }
1017
1018
    /**
1019
     * Get binary bitset in little-endian order.
1020
     *
1021
     * This is the integer bitset implementation.
1022
     *
1023
     * @return string
1024
     * @see getBinaryBitsetLe()
1025
     * @see doGetBinaryBitsetLeBin()
1026
     */
1027 2
    private function doGetBinaryBitsetLeInt()
1028
    {
1029 2
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
1030 2
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
1031
    }
1032
1033
    /**
1034
     * Get binary bitset in big-endian order
1035
     * 
1036
     * @return string
1037
     */
1038 1
    public function getBinaryBitsetBe(): string
1039
    {
1040 1
        return \strrev($this->bitset);
1041
    }
1042
1043
    /**
1044
     * Get a bit at the given ordinal number
1045
     *
1046
     * @param int $ordinal Ordinal number of bit to get
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
1047
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1048
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
1049
     * @uses doGetBitBin()
1050
     * @uses doGetBitInt()
1051
     */
1052 4
    public function getBit(int $ordinal): bool
1053
    {
1054 4
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
1055 1
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
1 ignored issue
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...
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 113 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1056
        }
1057
1058 3
        return $this->{$this->fnDoGetBit}($ordinal);
1059
    }
1060
1061
    /**
1062
     * Get a bit at the given ordinal number.
1063
     *
1064
     * This is the binary bitset implementation.
1065
     *
1066
     * @param int $ordinal Ordinal number of bit to get
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
1067
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1068
     * @see getBit()
1069
     * @see doGetBitInt()
1070
     */
1071 8
    private function doGetBitBin($ordinal)
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
1072
    {
1073 8
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
1074
    }
1075
1076
    /**
1077
     * Get a bit at the given ordinal number.
1078
     *
1079
     * This is the integer bitset implementation.
1080
     * 
1081
     * @param int $ordinal Ordinal number of bit to get
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
1082
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1083
     * @see getBit()
1084
     * @see doGetBitBin()
1085
     */
1086 18
    private function doGetBitInt($ordinal)
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
1087
    {
1088 18
        return (bool)($this->bitset & (1 << $ordinal));
1089
    }
1090
}
1091