Passed
Push — immutable-set ( d2c50e...4c41ff )
by Marc
02:14
created

EnumSet::doGetIteratorInt()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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