Completed
Pull Request — 4.x (#108)
by Marc
03:41 queued 01:51
created

EnumSet.php$0 ➔ valid()   A

Complexity

Conditions 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
c 0
b 0
f 0
cc 2
crap 2
rs 10
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>)
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 $fnDoCount             = 'doCountInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoCount" must contain a leading underscore
Loading history...
48
    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...
49
    private $fnDoGetBit            = 'doGetBitInt';
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 "fnDoGetBit" must contain a leading underscore
Loading history...
50
    private $fnDoSetBit            = 'doSetBitInt';
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 "fnDoSetBit" must contain a leading underscore
Loading history...
51
    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...
52
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
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 "fnDoGetBinaryBitsetLe" must contain a leading underscore
Loading history...
53
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
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 "fnDoSetBinaryBitsetLe" must contain a leading underscore
Loading history...
54
    /**#@-*/
55
56
    /**
57
     * Constructor
58
     *
59
     * @param string $enumeration The classname of the enumeration
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
60
     * @throws InvalidArgumentException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
61
     */
62 106
    public function __construct(string $enumeration)
63
    {
64 106
        if (!\is_subclass_of($enumeration, Enum::class)) {
65 1
            throw new InvalidArgumentException(\sprintf(
66 1
                '%s can handle subclasses of %s only',
67 1
                __METHOD__,
68 1
                Enum::class
69
            ));
70
        }
71
72 105
        $this->enumeration      = $enumeration;
73 105
        $this->enumerationCount = \count($enumeration::getConstants());
74
75
        // By default the bitset is initialized as integer bitset
76
        // in case the enumeraton has more enumerators then integer bits
77
        // we will switch this into a binary bitset
78 105
        if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
79
            // init binary bitset with zeros
80 25
            $this->bitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));
81
82
            // switch internal binary bitset functions
83 25
            $this->fnDoCount             = 'doCountBin';
84 25
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
85 25
            $this->fnDoGetBit            = 'doGetBitBin';
86 25
            $this->fnDoSetBit            = 'doSetBitBin';
87 25
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
88 25
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
89 25
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
90
        }
91 105
    }
92
93
    /**
94
     * Get the classname of the enumeration
95
     * @return string
96
     */
97 1
    public function getEnumeration(): string
98
    {
99 1
        return $this->enumeration;
100
    }
101
102
    /**
103
     * Attach a new enumerator or overwrite an existing one
104
     * @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...
105
     * @return void
106
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
107
     */
108 64
    public function attach($enumerator): void
109
    {
110 64
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
111 64
    }
112
113
    /**
114
     * Detach the given enumerator
115
     * @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...
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 9
    public function detach($enumerator): void
120
    {
121 9
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
122 9
    }
123
124
    /**
125
     * Test if the given enumerator was attached
126
     * @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...
127
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
128
     */
129 13
    public function contains($enumerator): bool
130
    {
131 13
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
132
    }
133
134
    /* IteratorAggregate */
135
136
    public function getIterator(): Iterator
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
137
    {
138
        return new class($this, $this->enumeration, $this->enumerationCount, $this->bitset) implements Iterator {
1 ignored issue
show
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...
139
            private $enumSet;
140
            private $enumeration;
141
            private $enumerationCount;
142
            private $ordinal = 0;
143
            private $fnDoRewind = 'doRewindInt';
144
145 15
            public function __construct(EnumSet $enumSet, $enumeration, $enumerationCount, &$bitset)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
146
            {
147 15
                $this->enumSet          = $enumSet;
148 15
                $this->enumeration      = $enumeration;
149 15
                $this->enumerationCount = $enumerationCount;
150 15
                $this->bitset           = &$bitset;
0 ignored issues
show
Bug Best Practice introduced by
The property bitset does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
151
152 15
                if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
153 5
                    $this->fnDoRewind = 'doRewindBin';
154
                }
155 15
            }
156
157
            /**
158
             * Get the current enumerator
159
             * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 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...
160
             */
161 11
            public function current(): ?Enum
162
            {
163 11
                if ($this->valid()) {
164 11
                    return ($this->enumeration)::byOrdinal($this->ordinal);
165
                }
166
167 2
                return null;
168
            }
169
170
            /**
171
             * Get the ordinal number of the current iterator position
172
             * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
173
             */
174 9
            public function key(): int
175
            {
176 9
                return $this->ordinal;
177
            }
178
179
            /**
180
             * Go to the next valid iterator position.
181
             * If no valid iterator position is found the iterator position will be the last possible + 1.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 106 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...
182
             * @return void
183
             */
184 15
            public function next(): void
185
            {
186
                do {
187 15
                    if (++$this->ordinal >= $this->enumerationCount) {
188 10
                        $this->ordinal = $this->enumerationCount;
189 10
                        return;
190
                    }
191 15
                } while (!$this->enumSet->getBit($this->ordinal));
192 15
            }
193
194
            /**
195
             * Go to the first valid iterator position.
196
             * If no valid iterator position was found the iterator position will be 0.
197
             * @return void
198
             * @uses doRewindBin()
199
             * @uses doRewindInt()
200
             */
201 12
            public function rewind(): void
202
            {
203 12
                $this->{$this->fnDoRewind}();
204 12
            }
205
206
            /**
207
             * Go to the first valid iterator position.
208
             * If no valid iterator position was found the iterator position will be 0.
209
             *
210
             * This is the binary bitset implementation.
211
             *
212
             * @return void
213
             * @see rewind()
214
             * @see doRewindInt()
215
             */
216 3
            private function doRewindBin(): void
217
            {
218 3
                if (\ltrim($this->bitset, "\0") !== '') {
219 3
                    $this->ordinal = -1;
220 3
                    $this->next();
221
                } else {
222 1
                    $this->ordinal = 0;
223
                }
224 3
            }
225
226
            /**
227
             * Go to the first valid iterator position.
228
             * If no valid iterator position was found the iterator position will be 0.
229
             *
230
             * This is the binary bitset implementation.
231
             *
232
             * @return void
233
             * @see rewind()
234
             * @see doRewindBin()
235
             */
236 9
            private function doRewindInt(): void
237
            {
238 9
                if ($this->bitset) {
239 9
                    $this->ordinal = -1;
240 9
                    $this->next();
241
                } else {
242 1
                    $this->ordinal = 0;
243
                }
244 9
            }
245
246
            /**
247
             * Test if the iterator is in a valid state
248
             * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
249
             */
250 13
            public function valid(): bool
251
            {
252 13
                return $this->ordinal !== $this->enumerationCount && $this->enumSet->getBit($this->ordinal);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 108 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...
253
            }
254
        };
255
    }
256
257
    /* Countable */
258
259
    /**
260
     * Count the number of elements
261
     *
262
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
263
     * @uses doCountBin()
264
     * @uses doCountInt()
265
     */
266 24
    public function count(): int
267
    {
268 24
        return $this->{$this->fnDoCount}();
269
    }
270
271
    /**
272
     * Count the number of elements.
273
     *
274
     * This is the binary bitset implementation.
275
     *
276
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
277
     * @see count()
278
     * @see doCountInt()
279
     */
280 10
    private function doCountBin(): int
281
    {
282 10
        $count   = 0;
283 10
        $bitset  = $this->bitset;
284 10
        $byteLen = \strlen($bitset);
285 10
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
286 10
            if ($bitset[$bytePos] === "\0") {
287
                // fast skip null byte
288 10
                continue;
289
            }
290
291 10
            $ord = \ord($bitset[$bytePos]);
292 10
            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...
293 10
            if ($ord & 0b00000010) ++$count;
0 ignored issues
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...
294 10
            if ($ord & 0b00000100) ++$count;
0 ignored issues
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...
295 10
            if ($ord & 0b00001000) ++$count;
0 ignored issues
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...
296 10
            if ($ord & 0b00010000) ++$count;
0 ignored issues
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...
297 10
            if ($ord & 0b00100000) ++$count;
0 ignored issues
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...
298 10
            if ($ord & 0b01000000) ++$count;
0 ignored issues
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...
299 10
            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...
300
        }
301 10
        return $count;
302
    }
303
304
    /**
305
     * Count the number of elements.
306
     *
307
     * This is the integer bitset implementation.
308
     *
309
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
310
     * @see count()
311
     * @see doCountBin()
312
     */
313 14
    private function doCountInt(): int
314
    {
315 14
        $count  = 0;
316 14
        $bitset = $this->bitset;
317
318
        // PHP does not support right shift unsigned
319 14
        if ($bitset < 0) {
320 4
            $count  = 1;
321 4
            $bitset = $bitset & \PHP_INT_MAX;
322
        }
323
324
        // iterate byte by byte and count set bits
325 14
        $phpIntBitSize = \PHP_INT_SIZE * 8;
326 14
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
327 14
            $bitChk = 0xff << $bitPos;
328 14
            $byte = $bitset & $bitChk;
329 14
            if ($byte) {
330 12
                $byte = $byte >> $bitPos;
331 12
                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...
332 12
                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...
333 12
                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...
334 12
                if ($byte & 0b00001000) ++$count;
0 ignored issues
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...
335 12
                if ($byte & 0b00010000) ++$count;
0 ignored issues
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...
336 12
                if ($byte & 0b00100000) ++$count;
0 ignored issues
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...
337 12
                if ($byte & 0b01000000) ++$count;
0 ignored issues
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...
338 12
                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...
339
            }
340
341 14
            if ($bitset <= $bitChk) {
342 12
                break;
343
            }
344
        }
345
346 14
        return $count;
347
    }
348
349
    /**
350
     * Check if this EnumSet is the same as other
351
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
352
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
353
     */
354 6
    public function isEqual(EnumSet $other): bool
355
    {
356 6
        return $this->enumeration === $other->enumeration
357 6
            && $this->bitset === $other->bitset;
358
    }
359
360
    /**
361
     * Check if this EnumSet is a subset of other
362
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
363
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
364
     */
365 8
    public function isSubset(EnumSet $other): bool
366
    {
367 8
        return $this->enumeration === $other->enumeration
368 8
            && ($this->bitset & $other->bitset) === $this->bitset;
369
    }
370
371
    /**
372
     * Check if this EnumSet is a superset of other
373
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
374
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
375
     */
376 8
    public function isSuperset(EnumSet $other): bool
377
    {
378 8
        return $this->enumeration === $other->enumeration
379 8
            && ($this->bitset | $other->bitset) === $this->bitset;
380
    }
381
382
    /**
383
     * Produce a new set with enumerators from both this and other (this | other)
384
     *
385
     * @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...
386
     * @return EnumSet
387
     * @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...
388
     */
389 4
    public function union(EnumSet $other): EnumSet
390
    {
391 4
        if ($this->enumeration !== $other->enumeration) {
392 2
            throw new InvalidArgumentException(\sprintf(
393 2
                'Other should be of the same enumeration as this %s',
394 2
                $this->enumeration
395
            ));
396
        }
397
398 2
        $clone = clone $this;
399 2
        $clone->bitset = $this->bitset | $other->bitset;
400 2
        return $clone;
401
    }
402
403
    /**
404
     * Produce a new set with enumerators common to both this and other (this & other)
405
     *
406
     * @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...
407
     * @return EnumSet
408
     * @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...
409
     */
410 4
    public function intersect(EnumSet $other): EnumSet
411
    {
412 4
        if ($this->enumeration !== $other->enumeration) {
413 2
            throw new InvalidArgumentException(\sprintf(
414 2
                'Other should be of the same enumeration as this %s',
415 2
                $this->enumeration
416
            ));
417
        }
418
419 2
        $clone = clone $this;
420 2
        $clone->bitset = $this->bitset & $other->bitset;
421 2
        return $clone;
422
    }
423
424
    /**
425
     * Produce a new set with enumerators in this but not in other (this - other)
426
     *
427
     * @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...
428
     * @return EnumSet
429
     * @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...
430
     */
431 4
    public function diff(EnumSet $other): EnumSet
432
    {
433 4
        if ($this->enumeration !== $other->enumeration) {
434 2
            throw new InvalidArgumentException(\sprintf(
435 2
                'Other should be of the same enumeration as this %s',
436 2
                $this->enumeration
437
            ));
438
        }
439
440 2
        $clone = clone $this;
441 2
        $clone->bitset = $this->bitset & ~$other->bitset;
442 2
        return $clone;
443
    }
444
445
    /**
446
     * Produce a new set with enumerators in either this and other but not in both (this ^ other)
447
     *
448
     * @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...
449
     * @return EnumSet
450
     * @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...
451
     */
452 4
    public function symDiff(EnumSet $other): EnumSet
453
    {
454 4
        if ($this->enumeration !== $other->enumeration) {
455 2
            throw new InvalidArgumentException(\sprintf(
456 2
                'Other should be of the same enumeration as this %s',
457 2
                $this->enumeration
458
            ));
459
        }
460
461 2
        $clone = clone $this;
462 2
        $clone->bitset = $this->bitset ^ $other->bitset;
463 2
        return $clone;
464
    }
465
466
    /**
467
     * Get ordinal numbers of the defined enumerators as array
468
     * @return int[]
469
     * @uses  doGetOrdinalsBin()
470
     * @uses  doGetOrdinalsInt()
471
     */
472 18
    public function getOrdinals(): array
473
    {
474 18
        return $this->{$this->fnDoGetOrdinals}();
475
    }
476
477
    /**
478
     * Get ordinal numbers of the defined enumerators as array.
479
     *
480
     * This is the binary bitset implementation.
481
     *
482
     * @return int[]
483
     * @see getOrdinals()
484
     * @see goGetOrdinalsInt()
485
     */
486 2
    private function doGetOrdinalsBin(): array
487
    {
488 2
        $ordinals = [];
489 2
        $bitset   = $this->bitset;
490 2
        $byteLen  = \strlen($bitset);
491 2
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
492 2
            if ($bitset[$bytePos] === "\0") {
493
                // fast skip null byte
494 2
                continue;
495
            }
496
497 2
            $ord = \ord($bitset[$bytePos]);
498 2
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
499 2
                if ($ord & (1 << $bitPos)) {
500 2
                    $ordinals[] = $bytePos * 8 + $bitPos;
501
                }
502
            }
503
        }
504 2
        return $ordinals;
505
    }
506
507
    /**
508
     * Get ordinal numbers of the defined enumerators as array.
509
     *
510
     * This is the integer bitset implementation.
511
     *
512
     * @return int[]
513
     * @see getOrdinals()
514
     * @see doGetOrdinalsBin()
515
     */
516 16
    private function doGetOrdinalsInt(): array
517
    {
518 16
        $ordinals   = [];
519 16
        $ordinalMax = $this->enumerationCount;
520 16
        $bitset     = $this->bitset;
521 16
        for ($ord = 0; $ord < $ordinalMax; ++$ord) {
522 16
            if ($bitset & (1 << $ord)) {
523 16
                $ordinals[] = $ord;
524
            }
525
        }
526 16
        return $ordinals;
527
    }
528
529
    /**
530
     * Get values of the defined enumerators as array
531
     * @return mixed[]
532
     */
533 10
    public function getValues(): array
534
    {
535 10
        $enumeration = $this->enumeration;
536 10
        $values      = [];
537 10
        foreach ($this->getOrdinals() as $ord) {
538 10
            $values[] = $enumeration::byOrdinal($ord)->getValue();
539
        }
540 10
        return $values;
541
    }
542
543
    /**
544
     * Get names of the defined enumerators as array
545
     * @return string[]
546
     */
547 2
    public function getNames(): array
548
    {
549 2
        $enumeration = $this->enumeration;
550 2
        $names       = [];
551 2
        foreach ($this->getOrdinals() as $ord) {
552 2
            $names[] = $enumeration::byOrdinal($ord)->getName();
553
        }
554 2
        return $names;
555
    }
556
557
    /**
558
     * Get the defined enumerators as array
559
     * @return Enum[]
560
     */
561 2
    public function getEnumerators(): array
562
    {
563 2
        $enumeration = $this->enumeration;
564 2
        $enumerators = [];
565 2
        foreach ($this->getOrdinals() as $ord) {
566 2
            $enumerators[] = $enumeration::byOrdinal($ord);
567
        }
568 2
        return $enumerators;
569
    }
570
571
    /**
572
     * Get binary bitset in little-endian order
573
     * 
574
     * @return string
575
     * @uses doGetBinaryBitsetLeBin()
576
     * @uses doGetBinaryBitsetLeInt()
577
     */
578 12
    public function getBinaryBitsetLe(): string
579
    {
580 12
        return $this->{$this->fnDoGetBinaryBitsetLe}();
581
    }
582
583
    /**
584
     * Get binary bitset in little-endian order.
585
     *
586
     * This is the binary bitset implementation.
587
     *
588
     * @return string
589
     * @see getBinaryBitsetLe()
590
     * @see doGetBinaryBitsetLeInt()
591
     */
592 8
    private function doGetBinaryBitsetLeBin(): string
593
    {
594 8
        return $this->bitset;
595
    }
596
597
    /**
598
     * Get binary bitset in little-endian order.
599
     *
600
     * This is the integer bitset implementation.
601
     *
602
     * @return string
603
     * @see getBinaryBitsetLe()
604
     * @see doGetBinaryBitsetLeBin()
605
     */
606 4
    private function doGetBinaryBitsetLeInt(): string
607
    {
608 4
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
609 4
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
610
    }
611
612
    /**
613
     * Set binary bitset in little-endian order
614
     *
615
     * NOTE: It resets the current position of the iterator
616
     * 
617
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
618
     * @return void
619
     * @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...
620
     * @uses doSetBinaryBitsetLeBin()
621
     * @uses doSetBinaryBitsetLeInt()
622
     */
623 24
    public function setBinaryBitsetLe(string $bitset): void
624
    {
625 24
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
626 12
    }
627
628
    /**
629
     * Set binary bitset in little-endian order
630
     *
631
     * NOTE: It resets the current position of the iterator
632
     *
633
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
634
     * @return void
635
     * @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...
636
     * @see setBinaryBitsetLeBin()
637
     * @see doSetBinaryBitsetLeInt()
638
     */
639 14
    private function doSetBinaryBitsetLeBin(string $bitset): void
640
    {
641 14
        $size   = \strlen($this->bitset);
642 14
        $sizeIn = \strlen($bitset);
643
644 14
        if ($sizeIn < $size) {
645
            // add "\0" if the given bitset is not long enough
646 2
            $bitset .= \str_repeat("\0", $size - $sizeIn);
647 12
        } elseif ($sizeIn > $size) {
648 4
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
649 2
                throw new InvalidArgumentException('out-of-range bits detected');
650
            }
651 2
            $bitset = \substr($bitset, 0, $size);
652
        }
653
654
        // truncate out-of-range bits of last byte
655 12
        $lastByteMaxOrd = $this->enumerationCount % 8;
656 12
        if ($lastByteMaxOrd !== 0) {
657 12
            $lastByte         = $bitset[-1];
658 12
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
659 12
            if ($lastByte !== $lastByteExpected) {
660 4
                throw new InvalidArgumentException('out-of-range bits detected');
661
            }
662
663 8
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
664
        }
665
666 8
        $this->bitset = $bitset;
667 8
    }
668
669
    /**
670
     * Set binary bitset in little-endian order
671
     *
672
     * NOTE: It resets the current position of the iterator
673
     *
674
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
675
     * @return void
676
     * @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...
677
     * @see setBinaryBitsetLeBin()
678
     * @see doSetBinaryBitsetLeBin()
679
     */
680 10
    private function doSetBinaryBitsetLeInt(string $bitset): void
681
    {
682 10
        $len = \strlen($bitset);
683 10
        $int = 0;
684 10
        for ($i = 0; $i < $len; ++$i) {
685 10
            $ord = \ord($bitset[$i]);
686
687 10
            if ($ord && $i > \PHP_INT_SIZE - 1) {
688 2
                throw new InvalidArgumentException('out-of-range bits detected');
689
            }
690
691 10
            $int |= $ord << (8 * $i);
692
        }
693
694 8
        if ($int & (~0 << $this->enumerationCount)) {
695 4
            throw new InvalidArgumentException('out-of-range bits detected');
696
        }
697
698 4
        $this->bitset = $int;
699 4
    }
700
701
    /**
702
     * Get binary bitset in big-endian order
703
     * 
704
     * @return string
705
     */
706 2
    public function getBinaryBitsetBe(): string
707
    {
708 2
        return \strrev($this->bitset);
709
    }
710
711
    /**
712
     * Set binary bitset in big-endian order
713
     *
714
     * NOTE: It resets the current position of the iterator
715
     * 
716
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
717
     * @return void
718
     * @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...
719
     */
720 2
    public function setBinaryBitsetBe(string $bitset): void
721
    {
722 2
        $this->setBinaryBitsetLe(\strrev($bitset));
723 2
    }
724
725
    /**
726
     * Get a bit at the given ordinal number
727
     *
728
     * @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...
729
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
730
     * @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...
731
     * @uses doGetBitBin()
732
     * @uses doGetBitInt()
733
     */
734 21
    public function getBit(int $ordinal): bool
735
    {
736 21
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
737 2
            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...
738
        }
739
740 19
        return $this->{$this->fnDoGetBit}($ordinal);
741
    }
742
743
    /**
744
     * Get a bit at the given ordinal number.
745
     *
746
     * This is the binary bitset implementation.
747
     *
748
     * @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...
749
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
750
     * @see getBit()
751
     * @see doGetBitInt()
752
     */
753 11
    private function doGetBitBin(int $ordinal): bool
754
    {
755 11
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
756
    }
757
758
    /**
759
     * Get a bit at the given ordinal number.
760
     *
761
     * This is the integer bitset implementation.
762
     * 
763
     * @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...
764
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
765
     * @see getBit()
766
     * @see doGetBitBin()
767
     */
768 16
    private function doGetBitInt(int $ordinal): bool
769
    {
770 16
        return (bool)($this->bitset & (1 << $ordinal));
771
    }
772
773
    /**
774
     * Set a bit at the given ordinal number
775
     *
776
     * @param int $ordinal Ordinal number of bit to set
2 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
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...
777
     * @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...
778
     * @return void
779
     * @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...
780
     * @uses doSetBitBin()
781
     * @uses doSetBitInt()
782
     * @uses doUnsetBitBin()
783
     * @uses doUnsetBitInt()
784
     */
785 4
    public function setBit(int $ordinal, bool $bit): void
786
    {
787 4
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
788 2
            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...
789
        }
790
791 2
        if ($bit) {
792 2
            $this->{$this->fnDoSetBit}($ordinal);
793
        } else {
794 2
            $this->{$this->fnDoUnsetBit}($ordinal);
795
        }
796 2
    }
797
798
    /**
799
     * Set a bit at the given ordinal number.
800
     *
801
     * This is the binary bitset implementation.
802
     * 
803
     * @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...
804
     * @return void
805
     * @see setBit()
806
     * @see doSetBitInt()
807
     */
808 11
    private function doSetBitBin(int $ordinal): void
809
    {
810 11
        $byte = (int) ($ordinal / 8);
811 11
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
812 11
    }
813
814
    /**
815
     * Set a bit at the given ordinal number.
816
     *
817
     * This is the binary bitset implementation.
818
     *
819
     * @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...
820
     * @return void
821
     * @see setBit()
822
     * @see doSetBitBin()
823
     */
824 55
    private function doSetBitInt(int $ordinal): void
825
    {
826 55
        $this->bitset = $this->bitset | (1 << $ordinal);
827 55
    }
828
829
    /**
830
     * Unset a bit at the given ordinal number.
831
     *
832
     * This is the binary bitset implementation.
833
     *
834
     * @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...
835
     * @return void
836
     * @see setBit()
837
     * @see doUnsetBitInt()
838
     */
839 5
    private function doUnsetBitBin(int $ordinal): void
840
    {
841 5
        $byte = (int) ($ordinal / 8);
842 5
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
843 5
    }
844
845
    /**
846
     * Unset a bit at the given ordinal number.
847
     *
848
     * This is the integer bitset implementation.
849
     *
850
     * @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...
851
     * @return void
852
     * @see setBit()
853
     * @see doUnsetBitBin()
854
     */
855 6
    private function doUnsetBitInt(int $ordinal): void
856
    {
857 6
        $this->bitset = $this->bitset & ~(1 << $ordinal);
858 6
    }
859
}
860