Passed
Push — EnumSet-generator ( 2205bb...83e5a4 )
by Marc
03:36
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
     * Attach a new enumerator or overwrite an existing one
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 void
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 attach($enumerator): void
112
    {
113 41
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
114 41
    }
115
116
    /**
117
     * Detach the given enumerator
118
     * @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...
119
     * @return void
120
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
121
     */
122 4
    public function detach($enumerator): void
123
    {
124 4
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
125 4
    }
126
127
    /**
128
     * Test if the given enumerator was attached
129
     * @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...
130
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
131
     */
132 11
    public function contains($enumerator): bool
133
    {
134 11
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
135
    }
136
137
    /* IteratorAggregate */
138
139
    /**
140
     * Get a new iterator
141
     * @return Iterator
142
     * @uses doGetIteratorInt()
143
     * @uses doGetIteratorBin()
144
     */
145 17
    public function getIterator(): Iterator
146
    {
147 17
        return $this->{$this->fnDoGetIterator}();
148
149
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
150
        $ordinal          = 0;
151
        $enumerationCount = $this->enumerationCount;
152
        $fnDoGetBit       = $this->fnDoGetBit;
153
        for (; $ordinal < $enumerationCount; ++$ordinal) {
154
            if ($this->{$fnDoGetBit}($ordinal)) {
155
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
156
            }
157
        }
158
        */
159
    }
160
161
    /**
162
     * Get a new Iterator.
163
     *
164
     * This is the binary bitset implementation.
165
     *
166
     * @return Iterator
167
     * @see getIterator()
168
     * @see goGetIteratorInt()
169
     */
170 5
    private function doGetIteratorBin(): Iterator
171
    {
172 5
        $bitset   = $this->bitset;
173 5
        $byteLen  = \strlen($bitset);
174 5
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
175 5
            if ($bitset[$bytePos] === "\0") {
176
                // fast skip null byte
177 3
                continue;
178
            }
179
180 5
            $ord = \ord($bitset[$bytePos]);
181 5
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
182 5
                if ($ord & (1 << $bitPos)) {
183 5
                    $ordinal = $bytePos * 8 + $bitPos;
184 5
                    yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
185
                }
186
            }
187
        }
188 5
    }
189
190
    /**
191
     * Get a new Iterator.
192
     *
193
     * This is the integer bitset implementation.
194
     *
195
     * @return Iterator
196
     * @see getIterator()
197
     * @see doGetIteratorBin()
198
     */
199 12
    private function doGetIteratorInt(): Iterator
200
    {
201 12
        $count  = $this->enumerationCount;
202 12
        $bitset = $this->bitset;
203 12
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
204 12
            if ($bitset & (1 << $ordinal)) {
205 10
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
206
            }
207
        }
208 10
    }
209
210
    /* Countable */
211
212
    /**
213
     * Count the number of elements
214
     *
215
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
216
     * @uses doCountBin()
217
     * @uses doCountInt()
218
     */
219 15
    public function count(): int
220
    {
221 15
        return $this->{$this->fnDoCount}();
222
    }
223
224
    /**
225
     * Count the number of elements.
226
     *
227
     * This is the binary bitset implementation.
228
     *
229
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
230
     * @see count()
231
     * @see doCountInt()
232
     */
233 7
    private function doCountBin(): int
234
    {
235 7
        $count   = 0;
236 7
        $bitset  = $this->bitset;
237 7
        $byteLen = \strlen($bitset);
238 7
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
239 7
            if ($bitset[$bytePos] === "\0") {
240
                // fast skip null byte
241 5
                continue;
242
            }
243
244 7
            $ord = \ord($bitset[$bytePos]);
245 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...
246 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...
247 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...
248 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...
249 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...
250 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...
251 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...
252 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...
253
        }
254 7
        return $count;
255
    }
256
257
    /**
258
     * Count the number of elements.
259
     *
260
     * This is the integer bitset implementation.
261
     *
262
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
263
     * @see count()
264
     * @see doCountBin()
265
     */
266 8
    private function doCountInt(): int
267
    {
268 8
        $count  = 0;
269 8
        $bitset = $this->bitset;
270
271
        // PHP does not support right shift unsigned
272 8
        if ($bitset < 0) {
273 2
            $count  = 1;
274 2
            $bitset = $bitset & \PHP_INT_MAX;
275
        }
276
277
        // iterate byte by byte and count set bits
278 8
        $phpIntBitSize = \PHP_INT_SIZE * 8;
279 8
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
280 8
            $bitChk = 0xff << $bitPos;
281 8
            $byte = $bitset & $bitChk;
282 8
            if ($byte) {
283 7
                $byte = $byte >> $bitPos;
284 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...
285 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...
286 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...
287 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...
288 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...
289 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...
290 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...
291 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...
292
            }
293
294 8
            if ($bitset <= $bitChk) {
295 7
                break;
296
            }
297
        }
298
299 8
        return $count;
300
    }
301
302
    /**
303
     * Check if this EnumSet is the same as other
304
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
305
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
306
     */
307 3
    public function isEqual(EnumSet $other): bool
308
    {
309 3
        return $this->enumeration === $other->enumeration
310 3
            && $this->bitset === $other->bitset;
311
    }
312
313
    /**
314
     * Check if this EnumSet is a subset of other
315
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
316
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
317
     */
318 4
    public function isSubset(EnumSet $other): bool
319
    {
320 4
        return $this->enumeration === $other->enumeration
321 4
            && ($this->bitset & $other->bitset) === $this->bitset;
322
    }
323
324
    /**
325
     * Check if this EnumSet is a superset of other
326
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
327
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
328
     */
329 4
    public function isSuperset(EnumSet $other): bool
330
    {
331 4
        return $this->enumeration === $other->enumeration
332 4
            && ($this->bitset | $other->bitset) === $this->bitset;
333
    }
334
335
    /**
336
     * Produce a new set with enumerators from both this and other (this | other)
337
     *
338
     * @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...
339
     * @return EnumSet
340
     * @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...
341
     */
342 2
    public function union(EnumSet $other): EnumSet
343
    {
344 2
        if ($this->enumeration !== $other->enumeration) {
345 1
            throw new InvalidArgumentException(\sprintf(
346 1
                'Other should be of the same enumeration as this %s',
347 1
                $this->enumeration
348
            ));
349
        }
350
351 1
        $clone = clone $this;
352 1
        $clone->bitset = $this->bitset | $other->bitset;
353 1
        return $clone;
354
    }
355
356
    /**
357
     * Produce a new set with enumerators common to both this and other (this & other)
358
     *
359
     * @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...
360
     * @return EnumSet
361
     * @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...
362
     */
363 2
    public function intersect(EnumSet $other): EnumSet
364
    {
365 2
        if ($this->enumeration !== $other->enumeration) {
366 1
            throw new InvalidArgumentException(\sprintf(
367 1
                'Other should be of the same enumeration as this %s',
368 1
                $this->enumeration
369
            ));
370
        }
371
372 1
        $clone = clone $this;
373 1
        $clone->bitset = $this->bitset & $other->bitset;
374 1
        return $clone;
375
    }
376
377
    /**
378
     * Produce a new set with enumerators in this but not in other (this - other)
379
     *
380
     * @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...
381
     * @return EnumSet
382
     * @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...
383
     */
384 2
    public function diff(EnumSet $other): EnumSet
385
    {
386 2
        if ($this->enumeration !== $other->enumeration) {
387 1
            throw new InvalidArgumentException(\sprintf(
388 1
                'Other should be of the same enumeration as this %s',
389 1
                $this->enumeration
390
            ));
391
        }
392
393 1
        $clone = clone $this;
394 1
        $clone->bitset = $this->bitset & ~$other->bitset;
395 1
        return $clone;
396
    }
397
398
    /**
399
     * Produce a new set with enumerators in either this and other but not in both (this ^ other)
400
     *
401
     * @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...
402
     * @return EnumSet
403
     * @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...
404
     */
405 2
    public function symDiff(EnumSet $other): EnumSet
406
    {
407 2
        if ($this->enumeration !== $other->enumeration) {
408 1
            throw new InvalidArgumentException(\sprintf(
409 1
                'Other should be of the same enumeration as this %s',
410 1
                $this->enumeration
411
            ));
412
        }
413
414 1
        $clone = clone $this;
415 1
        $clone->bitset = $this->bitset ^ $other->bitset;
416 1
        return $clone;
417
    }
418
419
    /**
420
     * Get ordinal numbers of the defined enumerators as array
421
     * @return int[]
422
     * @uses  doGetOrdinalsBin()
423
     * @uses  doGetOrdinalsInt()
424
     */
425 9
    public function getOrdinals(): array
426
    {
427 9
        return $this->{$this->fnDoGetOrdinals}();
428
    }
429
430
    /**
431
     * Get ordinal numbers of the defined enumerators as array.
432
     *
433
     * This is the binary bitset implementation.
434
     *
435
     * @return int[]
436
     * @see getOrdinals()
437
     * @see goGetOrdinalsInt()
438
     */
439 1
    private function doGetOrdinalsBin(): array
440
    {
441 1
        $ordinals = [];
442 1
        $bitset   = $this->bitset;
443 1
        $byteLen  = \strlen($bitset);
444 1
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
445 1
            if ($bitset[$bytePos] === "\0") {
446
                // fast skip null byte
447 1
                continue;
448
            }
449
450 1
            $ord = \ord($bitset[$bytePos]);
451 1
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
452 1
                if ($ord & (1 << $bitPos)) {
453 1
                    $ordinals[] = $bytePos * 8 + $bitPos;
454
                }
455
            }
456
        }
457 1
        return $ordinals;
458
    }
459
460
    /**
461
     * Get ordinal numbers of the defined enumerators as array.
462
     *
463
     * This is the integer bitset implementation.
464
     *
465
     * @return int[]
466
     * @see getOrdinals()
467
     * @see doGetOrdinalsBin()
468
     */
469 8
    private function doGetOrdinalsInt(): array
470
    {
471 8
        $ordinals = [];
472 8
        $count    = $this->enumerationCount;
473 8
        $bitset   = $this->bitset;
474 8
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
475 8
            if ($bitset & (1 << $ordinal)) {
476 8
                $ordinals[] = $ordinal;
477
            }
478
        }
479 8
        return $ordinals;
480
    }
481
482
    /**
483
     * Get values of the defined enumerators as array
484
     * @return mixed[]
485
     */
486 5
    public function getValues(): array
487
    {
488 5
        $enumeration = $this->enumeration;
489 5
        $values      = [];
490 5
        foreach ($this->getOrdinals() as $ord) {
491 5
            $values[] = $enumeration::byOrdinal($ord)->getValue();
492
        }
493 5
        return $values;
494
    }
495
496
    /**
497
     * Get names of the defined enumerators as array
498
     * @return string[]
499
     */
500 1
    public function getNames(): array
501
    {
502 1
        $enumeration = $this->enumeration;
503 1
        $names       = [];
504 1
        foreach ($this->getOrdinals() as $ord) {
505 1
            $names[] = $enumeration::byOrdinal($ord)->getName();
506
        }
507 1
        return $names;
508
    }
509
510
    /**
511
     * Get the defined enumerators as array
512
     * @return Enum[]
513
     */
514 1
    public function getEnumerators(): array
515
    {
516 1
        $enumeration = $this->enumeration;
517 1
        $enumerators = [];
518 1
        foreach ($this->getOrdinals() as $ord) {
519 1
            $enumerators[] = $enumeration::byOrdinal($ord);
520
        }
521 1
        return $enumerators;
522
    }
523
524
    /**
525
     * Get binary bitset in little-endian order
526
     * 
527
     * @return string
528
     * @uses doGetBinaryBitsetLeBin()
529
     * @uses doGetBinaryBitsetLeInt()
530
     */
531 6
    public function getBinaryBitsetLe(): string
532
    {
533 6
        return $this->{$this->fnDoGetBinaryBitsetLe}();
534
    }
535
536
    /**
537
     * Get binary bitset in little-endian order.
538
     *
539
     * This is the binary bitset implementation.
540
     *
541
     * @return string
542
     * @see getBinaryBitsetLe()
543
     * @see doGetBinaryBitsetLeInt()
544
     */
545 4
    private function doGetBinaryBitsetLeBin(): string
546
    {
547 4
        return $this->bitset;
548
    }
549
550
    /**
551
     * Get binary bitset in little-endian order.
552
     *
553
     * This is the integer bitset implementation.
554
     *
555
     * @return string
556
     * @see getBinaryBitsetLe()
557
     * @see doGetBinaryBitsetLeBin()
558
     */
559 2
    private function doGetBinaryBitsetLeInt(): string
560
    {
561 2
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
562 2
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
563
    }
564
565
    /**
566
     * Set binary bitset in little-endian order
567
     *
568
     * NOTE: It resets the current position of the iterator
569
     * 
570
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
571
     * @return void
572
     * @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...
573
     * @uses doSetBinaryBitsetLeBin()
574
     * @uses doSetBinaryBitsetLeInt()
575
     */
576 12
    public function setBinaryBitsetLe(string $bitset): void
577
    {
578 12
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
579 6
    }
580
581
    /**
582
     * Set binary bitset in little-endian order
583
     *
584
     * NOTE: It resets the current position of the iterator
585
     *
586
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
587
     * @return void
588
     * @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...
589
     * @see setBinaryBitsetLeBin()
590
     * @see doSetBinaryBitsetLeInt()
591
     */
592 7
    private function doSetBinaryBitsetLeBin(string $bitset): void
593
    {
594 7
        $size   = \strlen($this->bitset);
595 7
        $sizeIn = \strlen($bitset);
596
597 7
        if ($sizeIn < $size) {
598
            // add "\0" if the given bitset is not long enough
599 1
            $bitset .= \str_repeat("\0", $size - $sizeIn);
600 6
        } elseif ($sizeIn > $size) {
601 2
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
602 1
                throw new InvalidArgumentException('out-of-range bits detected');
603
            }
604 1
            $bitset = \substr($bitset, 0, $size);
605
        }
606
607
        // truncate out-of-range bits of last byte
608 6
        $lastByteMaxOrd = $this->enumerationCount % 8;
609 6
        if ($lastByteMaxOrd !== 0) {
610 6
            $lastByte         = $bitset[-1];
611 6
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
612 6
            if ($lastByte !== $lastByteExpected) {
613 2
                throw new InvalidArgumentException('out-of-range bits detected');
614
            }
615
616 4
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
617
        }
618
619 4
        $this->bitset = $bitset;
620 4
    }
621
622
    /**
623
     * Set binary bitset in little-endian order
624
     *
625
     * NOTE: It resets the current position of the iterator
626
     *
627
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
628
     * @return void
629
     * @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...
630
     * @see setBinaryBitsetLeBin()
631
     * @see doSetBinaryBitsetLeBin()
632
     */
633 5
    private function doSetBinaryBitsetLeInt(string $bitset): void
634
    {
635 5
        $len = \strlen($bitset);
636 5
        $int = 0;
637 5
        for ($i = 0; $i < $len; ++$i) {
638 5
            $ord = \ord($bitset[$i]);
639
640 5
            if ($ord && $i > \PHP_INT_SIZE - 1) {
641 1
                throw new InvalidArgumentException('out-of-range bits detected');
642
            }
643
644 5
            $int |= $ord << (8 * $i);
645
        }
646
647 4
        if ($int & (~0 << $this->enumerationCount)) {
648 2
            throw new InvalidArgumentException('out-of-range bits detected');
649
        }
650
651 2
        $this->bitset = $int;
652 2
    }
653
654
    /**
655
     * Get binary bitset in big-endian order
656
     * 
657
     * @return string
658
     */
659 1
    public function getBinaryBitsetBe(): string
660
    {
661 1
        return \strrev($this->bitset);
662
    }
663
664
    /**
665
     * Set binary bitset in big-endian order
666
     *
667
     * NOTE: It resets the current position of the iterator
668
     * 
669
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
670
     * @return void
671
     * @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...
672
     */
673 1
    public function setBinaryBitsetBe(string $bitset): void
674
    {
675 1
        $this->setBinaryBitsetLe(\strrev($bitset));
676 1
    }
677
678
    /**
679
     * Get a bit at the given ordinal number
680
     *
681
     * @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...
682
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
683
     * @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...
684
     * @uses doGetBitBin()
685
     * @uses doGetBitInt()
686
     */
687 3
    public function getBit(int $ordinal): bool
688
    {
689 3
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
690 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...
691
        }
692
693 2
        return $this->{$this->fnDoGetBit}($ordinal);
694
    }
695
696
    /**
697
     * Get a bit at the given ordinal number.
698
     *
699
     * This is the binary bitset implementation.
700
     *
701
     * @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...
702
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
703
     * @see getBit()
704
     * @see doGetBitInt()
705
     */
706 5
    private function doGetBitBin(int $ordinal): bool
707
    {
708 5
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
709
    }
710
711
    /**
712
     * Get a bit at the given ordinal number.
713
     *
714
     * This is the integer bitset implementation.
715
     * 
716
     * @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...
717
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
718
     * @see getBit()
719
     * @see doGetBitBin()
720
     */
721 7
    private function doGetBitInt(int $ordinal): bool
722
    {
723 7
        return (bool)($this->bitset & (1 << $ordinal));
724
    }
725
726
    /**
727
     * Set a bit at the given ordinal number
728
     *
729
     * @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...
730
     * @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...
731
     * @return void
732
     * @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...
733
     * @uses doSetBitBin()
734
     * @uses doSetBitInt()
735
     * @uses doUnsetBitBin()
736
     * @uses doUnsetBitInt()
737
     */
738 2
    public function setBit(int $ordinal, bool $bit): void
739
    {
740 2
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
741 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...
742
        }
743
744 1
        if ($bit) {
745 1
            $this->{$this->fnDoSetBit}($ordinal);
746
        } else {
747 1
            $this->{$this->fnDoUnsetBit}($ordinal);
748
        }
749 1
    }
750
751
    /**
752
     * Set a bit at the given ordinal number.
753
     *
754
     * This is the binary bitset implementation.
755
     * 
756
     * @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...
757
     * @return void
758
     * @see setBit()
759
     * @see doSetBitInt()
760
     */
761 8
    private function doSetBitBin(int $ordinal): void
762
    {
763 8
        $byte = (int) ($ordinal / 8);
764 8
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
765 8
    }
766
767
    /**
768
     * Set a bit at the given ordinal number.
769
     *
770
     * This is the binary bitset implementation.
771
     *
772
     * @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...
773
     * @return void
774
     * @see setBit()
775
     * @see doSetBitBin()
776
     */
777 34
    private function doSetBitInt(int $ordinal): void
778
    {
779 34
        $this->bitset = $this->bitset | (1 << $ordinal);
780 34
    }
781
782
    /**
783
     * Unset a bit at the given ordinal number.
784
     *
785
     * This is the binary bitset implementation.
786
     *
787
     * @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...
788
     * @return void
789
     * @see setBit()
790
     * @see doUnsetBitInt()
791
     */
792 2
    private function doUnsetBitBin(int $ordinal): void
793
    {
794 2
        $byte = (int) ($ordinal / 8);
795 2
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
796 2
    }
797
798
    /**
799
     * Unset a bit at the given ordinal number.
800
     *
801
     * This is the integer bitset implementation.
802
     *
803
     * @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...
804
     * @return void
805
     * @see setBit()
806
     * @see doUnsetBitBin()
807
     */
808 3
    private function doUnsetBitInt(int $ordinal): void
809
    {
810 3
        $this->bitset = $this->bitset & ~(1 << $ordinal);
811 3
    }
812
}
813