Completed
Pull Request — master (#123)
by Marc
73:34 queued 71:19
created

EnumSet::addIterable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 3
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
     * @see with()
163
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
164
     */
165 1
    public function attach($enumerator): void
166
    {
167 1
        $this->add($enumerator);
168 1
    }
169
170
    /**
171
     * Removes the given enumerator object or value
172
     * @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...
173
     * @return void
174
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
175
     * @see remove()
176
     * @see without()
177
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
178
     */
179 1
    public function detach($enumerator): void
180
    {
181 1
        $this->remove($enumerator);
182 1
    }
183
184
    /**
185
     * Removes all enumerator objects or values of the given iterable
186
     * @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...
187
     * @return void
188
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
189
     */
190 6
    public function removeIterable(iterable $enumerators): void
191
    {
192 6
        $bitset = $this->bitset;
193
194
        try {
195 6
            foreach ($enumerators as $enumerator) {
196 6
                $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
197
            }
198 1
        } catch (\Throwable $e) {
199
            // reset all changes until error happened
200 1
            $this->bitset = $bitset;
201 1
            throw $e;
202
        }
203 5
    }
204
205
    /**
206
     * Modify this set from both this and other (this | other)
207
     *
208
     * @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...
209
     * @return void
210
     * @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...
211
     */
212 4
    public function setUnion(EnumSet $other): void
213
    {
214 4
        if ($this->enumeration !== $other->enumeration) {
215 1
            throw new InvalidArgumentException(\sprintf(
216 1
                'Other should be of the same enumeration as this %s',
217 1
                $this->enumeration
218
            ));
219
        }
220
221 3
        $this->bitset = $this->bitset | $other->bitset;
222 3
    }
223
224
    /**
225
     * Modify this set with enumerators common to both this and other (this & other)
226
     *
227
     * @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...
228
     * @return void
229
     * @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...
230
     */
231 4
    public function setIntersect(EnumSet $other): void
232
    {
233 4
        if ($this->enumeration !== $other->enumeration) {
234 1
            throw new InvalidArgumentException(\sprintf(
235 1
                'Other should be of the same enumeration as this %s',
236 1
                $this->enumeration
237
            ));
238
        }
239
240 3
        $this->bitset = $this->bitset & $other->bitset;
241 3
    }
242
243
    /**
244
     * Modify this set with enumerators in this but not in other (this - other)
245
     *
246
     * @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...
247
     * @return void
248
     * @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...
249
     */
250 4
    public function setDiff(EnumSet $other): void
251
    {
252 4
        if ($this->enumeration !== $other->enumeration) {
253 1
            throw new InvalidArgumentException(\sprintf(
254 1
                'Other should be of the same enumeration as this %s',
255 1
                $this->enumeration
256
            ));
257
        }
258
259 3
        $this->bitset = $this->bitset & ~$other->bitset;
260 3
    }
261
262
    /**
263
     * Modify this set with enumerators in either this and other but not in both (this ^ other)
264
     *
265
     * @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...
266
     * @return void
267
     * @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...
268
     */
269 4
    public function setSymDiff(EnumSet $other): void
270
    {
271 4
        if ($this->enumeration !== $other->enumeration) {
272 1
            throw new InvalidArgumentException(\sprintf(
273 1
                'Other should be of the same enumeration as this %s',
274 1
                $this->enumeration
275
            ));
276
        }
277
278 3
        $this->bitset = $this->bitset ^ $other->bitset;
279 3
    }
280
281
    /**
282
     * Set the given binary bitset in little-endian order
283
     *
284
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
285
     * @return void
286
     * @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...
287
     * @uses doSetBinaryBitsetLeBin()
288
     * @uses doSetBinaryBitsetLeInt()
289
     */
290 1
    public function setBinaryBitsetLe(string $bitset): void
291
    {
292 1
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
293 1
    }
294
295
    /**
296
     * Set binary bitset in little-endian order
297
     *
298
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
299
     * @return void
300
     * @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...
301
     * @see setBinaryBitsetLeBin()
302
     * @see doSetBinaryBitsetLeInt()
303
     */
304 9
    private function doSetBinaryBitsetLeBin($bitset): void
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $bitset
Loading history...
305
    {
306 9
        $size   = \strlen($this->bitset);
307 9
        $sizeIn = \strlen($bitset);
308
309 9
        if ($sizeIn < $size) {
310
            // add "\0" if the given bitset is not long enough
311 1
            $bitset .= \str_repeat("\0", $size - $sizeIn);
312 8
        } elseif ($sizeIn > $size) {
313 2
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
314 1
                throw new InvalidArgumentException('out-of-range bits detected');
315
            }
316 1
            $bitset = \substr($bitset, 0, $size);
317
        }
318
319
        // truncate out-of-range bits of last byte
320 8
        $lastByteMaxOrd = $this->enumerationCount % 8;
321 8
        if ($lastByteMaxOrd !== 0) {
322 8
            $lastByte         = $bitset[-1];
323 8
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
324 8
            if ($lastByte !== $lastByteExpected) {
325 2
                throw new InvalidArgumentException('out-of-range bits detected');
326
            }
327
328 6
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
329
        }
330
331 6
        $this->bitset = $bitset;
332 6
    }
333
334
    /**
335
     * Set binary bitset in little-endian order
336
     *
337
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
338
     * @return void
339
     * @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...
340
     * @see setBinaryBitsetLeBin()
341
     * @see doSetBinaryBitsetLeBin()
342
     */
343 5
    private function doSetBinaryBitsetLeInt($bitset): void
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $bitset
Loading history...
344
    {
345 5
        $len = \strlen($bitset);
346 5
        $int = 0;
347 5
        for ($i = 0; $i < $len; ++$i) {
348 5
            $ord = \ord($bitset[$i]);
349
350 5
            if ($ord && $i > \PHP_INT_SIZE - 1) {
351 1
                throw new InvalidArgumentException('out-of-range bits detected');
352
            }
353
354 5
            $int |= $ord << (8 * $i);
355
        }
356
357 4
        if ($int & (~0 << $this->enumerationCount)) {
358 2
            throw new InvalidArgumentException('out-of-range bits detected');
359
        }
360
361 2
        $this->bitset = $int;
362 2
    }
363
364
    /**
365
     * Set the given binary bitset in big-endian order
366
     *
367
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
368
     * @return void
369
     * @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...
370
     */
371 1
    public function setBinaryBitsetBe(string $bitset): void
372
    {
373 1
        $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
374 1
    }
375
376
    /**
377
     * Set a bit at the given ordinal number
378
     *
379
     * @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...
380
     * @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...
381
     * @return void
382
     * @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...
383
     * @uses doSetBitBin()
384
     * @uses doSetBitInt()
385
     * @uses doUnsetBitBin()
386
     * @uses doUnsetBitInt()
387
     */
388 3
    public function setBit(int $ordinal, bool $bit): void
389
    {
390 3
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
391 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...
392
        }
393
394 2
        if ($bit) {
395 2
            $this->{$this->fnDoSetBit}($ordinal);
396
        } else {
397 2
            $this->{$this->fnDoUnsetBit}($ordinal);
398
        }
399 2
    }
400
401
    /**
402
     * Set a bit at the given ordinal number.
403
     *
404
     * This is the binary bitset implementation.
405
     *
406
     * @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...
407
     * @return void
408
     * @see setBit()
409
     * @see doSetBitInt()
410
     */
411 14
    private function doSetBitBin($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
412
    {
413 14
        $byte = (int) ($ordinal / 8);
414 14
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
415 14
    }
416
417
    /**
418
     * Set a bit at the given ordinal number.
419
     *
420
     * This is the binary bitset implementation.
421
     *
422
     * @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...
423
     * @return void
424
     * @see setBit()
425
     * @see doSetBitBin()
426
     */
427 60
    private function doSetBitInt($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
428
    {
429 60
        $this->bitset = $this->bitset | (1 << $ordinal);
430 60
    }
431
432
    /**
433
     * Unset a bit at the given ordinal number.
434
     *
435
     * This is the binary bitset implementation.
436
     *
437
     * @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...
438
     * @return void
439
     * @see setBit()
440
     * @see doUnsetBitInt()
441
     */
442 10
    private function doUnsetBitBin($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
443
    {
444 10
        $byte = (int) ($ordinal / 8);
445 10
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
446 10
    }
447
448
    /**
449
     * Unset a bit at the given ordinal number.
450
     *
451
     * This is the integer bitset implementation.
452
     *
453
     * @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...
454
     * @return void
455
     * @see setBit()
456
     * @see doUnsetBitBin()
457
     */
458 20
    private function doUnsetBitInt($ordinal): void
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
459
    {
460 20
        $this->bitset = $this->bitset & ~(1 << $ordinal);
461 20
    }
462
463
    /* write access (immutable) */
464
465
    /**
466
     * Creates a new set with the given enumerator object or value added
467
     * @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...
468
     * @return static
469
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
470
     */
471 28
    public function with($enumerator): self
472
    {
473 28
        $clone = clone $this;
474 28
        $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
475 28
        return $clone;
476
    }
477
478
    /**
479
     * Creates a new set with the given enumeration objects or values added
480
     * @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...
481
     * @return static
482
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
483
     */
484 5
    public function withIterable(iterable $enumerators): self
485
    {
486 5
        $clone = clone $this;
487 5
        foreach ($enumerators as $enumerator) {
488 5
            $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
489
        }
490 5
        return $clone;
491
    }
492
493
    /**
494
     * Create a new set with the given enumerator object or value removed
495
     * @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...
496
     * @return static
497
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
498
     */
499 9
    public function without($enumerator): self
500
    {
501 9
        $clone = clone $this;
502 9
        $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
503 9
        return $clone;
504
    }
505
506
    /**
507
     * Creates a new set with the given enumeration objects or values removed
508
     * @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...
509
     * @return static
510
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
511
     */
512 5
    public function withoutIterable(iterable $enumerators): self
513
    {
514 5
        $clone = clone $this;
515 5
        foreach ($enumerators as $enumerator) {
516 5
            $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
517
        }
518 5
        return $clone;
519
    }
520
521
    /**
522
     * Create a new set with enumerators from both this and other (this | other)
523
     *
524
     * @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...
525
     * @return static
526
     * @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...
527
     */
528 2
    public function withUnion(EnumSet $other): self
529
    {
530 2
        $clone = clone $this;
531 2
        $clone->setUnion($other);
532 2
        return $clone;
533
    }
534
535
    /**
536
     * Create a new set with enumerators from both this and other (this | other)
537
     *
538
     * @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...
539
     * @return static
540
     * @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...
541
     * @see withUnion()
542
     * @see setUnion()
543
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
544
     */
545 1
    public function union(EnumSet $other): self
546
    {
547 1
        return $this->withUnion($other);
548
    }
549
550
    /**
551
     * Create a new set with enumerators common to both this and other (this & other)
552
     *
553
     * @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...
554
     * @return static
555
     * @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...
556
     */
557 2
    public function withIntersect(EnumSet $other): self
558
    {
559 2
        $clone = clone $this;
560 2
        $clone->setIntersect($other);
561 2
        return $clone;
562
    }
563
564
    /**
565
     * Create a new set with enumerators common to both this and other (this & other)
566
     *
567
     * @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...
568
     * @return static
569
     * @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...
570
     * @see withIntersect()
571
     * @see setIntersect()
572
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
573
     */
574 1
    public function intersect(EnumSet $other): self
575
    {
576 1
        return $this->withIntersect($other);
577
    }
578
579
    /**
580
     * Create a new set with enumerators in this but not in other (this - other)
581
     *
582
     * @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...
583
     * @return static
584
     * @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...
585
     */
586 2
    public function withDiff(EnumSet $other): self
587
    {
588 2
        $clone = clone $this;
589 2
        $clone->setDiff($other);
590 2
        return $clone;
591
    }
592
593
    /**
594
     * Create a new set with enumerators in this but not in other (this - other)
595
     *
596
     * @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...
597
     * @return static
598
     * @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...
599
     * @see withDiff()
600
     * @see setDiff()
601
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
602
     */
603 1
    public function diff(EnumSet $other): self
604
    {
605 1
        return $this->withDiff($other);
606
    }
607
608
    /**
609
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
610
     *
611
     * @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...
612
     * @return static
613
     * @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...
614
     */
615 2
    public function withSymDiff(EnumSet $other): self
616
    {
617 2
        $clone = clone $this;
618 2
        $clone->setSymDiff($other);
619 2
        return $clone;
620
    }
621
622
    /**
623
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
624
     *
625
     * @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...
626
     * @return static
627
     * @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...
628
     * @see withSymDiff()
629
     * @see setSymDiff()
630
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
631
     */
632 1
    public function symDiff(EnumSet $other): self
633
    {
634 1
        return $this->withSymDiff($other);
635
    }
636
637
    /**
638
     * Create a new set with the given binary bitset in little-endian order
639
     *
640
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
641
     * @return static
642
     * @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...
643
     * @uses doSetBinaryBitsetLeBin()
644
     * @uses doSetBinaryBitsetLeInt()
645
     */
646 11
    public function withBinaryBitsetLe(string $bitset): self
647
    {
648 11
        $clone = clone $this;
649 11
        $clone->{$this->fnDoSetBinaryBitsetLe}($bitset);
650 5
        return $clone;
651
    }
652
653
    /**
654
     * Create a new set with the given binary bitset in big-endian order
655
     *
656
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
657
     * @return static
658
     * @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...
659
     */
660 1
    public function withBinaryBitsetBe(string $bitset): self
661
    {
662 1
        $clone = $this;
663 1
        $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
664 1
        return $clone;
665
    }
666
667
    /**
668
     * Create a new set with the bit at the given ordinal number set
669
     *
670
     * @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...
671
     * @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...
672
     * @return static
673
     * @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...
674
     * @uses doSetBitBin()
675
     * @uses doSetBitInt()
676
     * @uses doUnsetBitBin()
677
     * @uses doUnsetBitInt()
678
     */
679 1
    public function withBit(int $ordinal, bool $bit): self
680
    {
681 1
        $clone = clone $this;
682 1
        $clone->setBit($ordinal, $bit);
683 1
        return $clone;
684
    }
685
686
    /* read access */
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
     */
693 24
    public function has($enumerator): bool
694
    {
695 24
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
696
    }
697
698
    /**
699
     * Test if the given enumerator exists
700
     * @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...
701
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
702
     * @see has()
703
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
704
     */
705 1
    public function contains($enumerator): bool
706
    {
707 1
        return $this->has($enumerator);
708
    }
709
710
    /* IteratorAggregate */
711
712
    /**
713
     * Get a new iterator
714
     * @return Iterator
715
     * @uses doGetIteratorInt()
716
     * @uses doGetIteratorBin()
717
     */
718 13
    public function getIterator(): Iterator
719
    {
720 13
        return $this->{$this->fnDoGetIterator}();
721
    }
722
723
    /**
724
     * Get a new Iterator.
725
     *
726
     * This is the binary bitset implementation.
727
     *
728
     * @return Iterator
729
     * @see getIterator()
730
     * @see goGetIteratorInt()
731
     */
732 4
    private function doGetIteratorBin()
733
    {
734 4
        $bitset   = $this->bitset;
735 4
        $byteLen  = \strlen($bitset);
736 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
737 4
            if ($bitset[$bytePos] === "\0") {
738
                // fast skip null byte
739 4
                continue;
740
            }
741
742 4
            $ord = \ord($bitset[$bytePos]);
743 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
744 4
                if ($ord & (1 << $bitPos)) {
745 4
                    $ordinal = $bytePos * 8 + $bitPos;
746 4
                    yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
747
                }
748
            }
749
        }
750 4
    }
751
752
    /**
753
     * Get a new Iterator.
754
     *
755
     * This is the integer bitset implementation.
756
     *
757
     * @return Iterator
758
     * @see getIterator()
759
     * @see doGetIteratorBin()
760
     */
761 9
    private function doGetIteratorInt()
762
    {
763 9
        $count  = $this->enumerationCount;
764 9
        $bitset = $this->bitset;
765 9
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
766 9
            if ($bitset & (1 << $ordinal)) {
767 7
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
768
            }
769
        }
770 7
    }
771
772
    /* Countable */
773
774
    /**
775
     * Count the number of elements
776
     *
777
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
778
     * @uses doCountBin()
779
     * @uses doCountInt()
780
     */
781 32
    public function count(): int
782
    {
783 32
        return $this->{$this->fnDoCount}();
784
    }
785
786
    /**
787
     * Count the number of elements.
788
     *
789
     * This is the binary bitset implementation.
790
     *
791
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
792
     * @see count()
793
     * @see doCountInt()
794
     */
795 15
    private function doCountBin()
796
    {
797 15
        $count   = 0;
798 15
        $bitset  = $this->bitset;
799 15
        $byteLen = \strlen($bitset);
800 15
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
801 15
            if ($bitset[$bytePos] === "\0") {
802
                // fast skip null byte
803 15
                continue;
804
            }
805
806 15
            $ord = \ord($bitset[$bytePos]);
807 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...
808 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...
809 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...
810 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...
811 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...
812 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...
813 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...
814 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...
815
        }
816 15
        return $count;
817
    }
818
819
    /**
820
     * Count the number of elements.
821
     *
822
     * This is the integer bitset implementation.
823
     *
824
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
825
     * @see count()
826
     * @see doCountBin()
827
     */
828 17
    private function doCountInt()
829
    {
830 17
        $count  = 0;
831 17
        $bitset = $this->bitset;
832
833
        // PHP does not support right shift unsigned
834 17
        if ($bitset < 0) {
835 5
            $count  = 1;
836 5
            $bitset = $bitset & \PHP_INT_MAX;
837
        }
838
839
        // iterate byte by byte and count set bits
840 17
        $phpIntBitSize = \PHP_INT_SIZE * 8;
841 17
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
842 17
            $bitChk = 0xff << $bitPos;
843 17
            $byte = $bitset & $bitChk;
844 17
            if ($byte) {
845 16
                $byte = $byte >> $bitPos;
846 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...
847 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...
848 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...
849 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...
850 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...
851 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...
852 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...
853 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...
854
            }
855
856 17
            if ($bitset <= $bitChk) {
857 17
                break;
858
            }
859
        }
860
861 17
        return $count;
862
    }
863
864
    /**
865
     * Check if this EnumSet is the same as other
866
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
867
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
868
     */
869 3
    public function isEqual(EnumSet $other): bool
870
    {
871 3
        return $this->enumeration === $other->enumeration
872 3
            && $this->bitset === $other->bitset;
873
    }
874
875
    /**
876
     * Check if this EnumSet is a subset of other
877
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
878
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
879
     */
880 4
    public function isSubset(EnumSet $other): bool
881
    {
882 4
        return $this->enumeration === $other->enumeration
883 4
            && ($this->bitset & $other->bitset) === $this->bitset;
884
    }
885
886
    /**
887
     * Check if this EnumSet is a superset of other
888
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
889
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
890
     */
891 4
    public function isSuperset(EnumSet $other): bool
892
    {
893 4
        return $this->enumeration === $other->enumeration
894 4
            && ($this->bitset | $other->bitset) === $this->bitset;
895
    }
896
897
    /**
898
     * Get ordinal numbers of the defined enumerators as array
899
     * @return int[]
900
     * @uses  doGetOrdinalsBin()
901
     * @uses  doGetOrdinalsInt()
902
     */
903 18
    public function getOrdinals(): array
904
    {
905 18
        return $this->{$this->fnDoGetOrdinals}();
906
    }
907
908
    /**
909
     * Get ordinal numbers of the defined enumerators as array.
910
     *
911
     * This is the binary bitset implementation.
912
     *
913
     * @return int[]
914
     * @see getOrdinals()
915
     * @see goGetOrdinalsInt()
916
     */
917 1
    private function doGetOrdinalsBin()
918
    {
919 1
        $ordinals = [];
920 1
        $bitset   = $this->bitset;
921 1
        $byteLen  = \strlen($bitset);
922 1
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
923 1
            if ($bitset[$bytePos] === "\0") {
924
                // fast skip null byte
925 1
                continue;
926
            }
927
928 1
            $ord = \ord($bitset[$bytePos]);
929 1
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
930 1
                if ($ord & (1 << $bitPos)) {
931 1
                    $ordinals[] = $bytePos * 8 + $bitPos;
932
                }
933
            }
934
        }
935 1
        return $ordinals;
936
    }
937
938
    /**
939
     * Get ordinal numbers of the defined enumerators as array.
940
     *
941
     * This is the integer bitset implementation.
942
     *
943
     * @return int[]
944
     * @see getOrdinals()
945
     * @see doGetOrdinalsBin()
946
     */
947 17
    private function doGetOrdinalsInt()
948
    {
949 17
        $ordinals = [];
950 17
        $count    = $this->enumerationCount;
951 17
        $bitset   = $this->bitset;
952 17
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
953 17
            if ($bitset & (1 << $ordinal)) {
954 17
                $ordinals[] = $ordinal;
955
            }
956
        }
957 17
        return $ordinals;
958
    }
959
960
    /**
961
     * Get values of the defined enumerators as array
962
     * @return mixed[]
963
     */
964 13
    public function getValues(): array
965
    {
966 13
        $enumeration = $this->enumeration;
967 13
        $values      = [];
968 13
        foreach ($this->getOrdinals() as $ord) {
969 13
            $values[] = $enumeration::byOrdinal($ord)->getValue();
970
        }
971 13
        return $values;
972
    }
973
974
    /**
975
     * Get names of the defined enumerators as array
976
     * @return string[]
977
     */
978 1
    public function getNames(): array
979
    {
980 1
        $enumeration = $this->enumeration;
981 1
        $names       = [];
982 1
        foreach ($this->getOrdinals() as $ord) {
983 1
            $names[] = $enumeration::byOrdinal($ord)->getName();
984
        }
985 1
        return $names;
986
    }
987
988
    /**
989
     * Get the defined enumerators as array
990
     * @return Enum[]
991
     */
992 2
    public function getEnumerators(): array
993
    {
994 2
        $enumeration = $this->enumeration;
995 2
        $enumerators = [];
996 2
        foreach ($this->getOrdinals() as $ord) {
997 2
            $enumerators[] = $enumeration::byOrdinal($ord);
998
        }
999 2
        return $enumerators;
1000
    }
1001
1002
    /**
1003
     * Get binary bitset in little-endian order
1004
     * 
1005
     * @return string
1006
     * @uses doGetBinaryBitsetLeBin()
1007
     * @uses doGetBinaryBitsetLeInt()
1008
     */
1009 6
    public function getBinaryBitsetLe(): string
1010
    {
1011 6
        return $this->{$this->fnDoGetBinaryBitsetLe}();
1012
    }
1013
1014
    /**
1015
     * Get binary bitset in little-endian order.
1016
     *
1017
     * This is the binary bitset implementation.
1018
     *
1019
     * @return string
1020
     * @see getBinaryBitsetLe()
1021
     * @see doGetBinaryBitsetLeInt()
1022
     */
1023 4
    private function doGetBinaryBitsetLeBin()
1024
    {
1025 4
        return $this->bitset;
1026
    }
1027
1028
    /**
1029
     * Get binary bitset in little-endian order.
1030
     *
1031
     * This is the integer bitset implementation.
1032
     *
1033
     * @return string
1034
     * @see getBinaryBitsetLe()
1035
     * @see doGetBinaryBitsetLeBin()
1036
     */
1037 2
    private function doGetBinaryBitsetLeInt()
1038
    {
1039 2
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
1040 2
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
1041
    }
1042
1043
    /**
1044
     * Get binary bitset in big-endian order
1045
     * 
1046
     * @return string
1047
     */
1048 1
    public function getBinaryBitsetBe(): string
1049
    {
1050 1
        return \strrev($this->bitset);
1051
    }
1052
1053
    /**
1054
     * Get a bit at the given ordinal number
1055
     *
1056
     * @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...
1057
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1058
     * @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...
1059
     * @uses doGetBitBin()
1060
     * @uses doGetBitInt()
1061
     */
1062 4
    public function getBit(int $ordinal): bool
1063
    {
1064 4
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
1065 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...
1066
        }
1067
1068 3
        return $this->{$this->fnDoGetBit}($ordinal);
1069
    }
1070
1071
    /**
1072
     * Get a bit at the given ordinal number.
1073
     *
1074
     * This is the binary bitset implementation.
1075
     *
1076
     * @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...
1077
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1078
     * @see getBit()
1079
     * @see doGetBitInt()
1080
     */
1081 8
    private function doGetBitBin($ordinal)
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
1082
    {
1083 8
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
1084
    }
1085
1086
    /**
1087
     * Get a bit at the given ordinal number.
1088
     *
1089
     * This is the integer bitset implementation.
1090
     * 
1091
     * @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...
1092
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
1093
     * @see getBit()
1094
     * @see doGetBitBin()
1095
     */
1096 18
    private function doGetBitInt($ordinal)
0 ignored issues
show
Coding Style introduced by
Type hint "int" missing for $ordinal
Loading history...
1097
    {
1098 18
        return (bool)($this->bitset & (1 << $ordinal));
1099
    }
1100
}
1101