Completed
Pull Request — 4.x (#110)
by Marc
07:52 queued 03:59
created

EnumSet::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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