Completed
Pull Request — 4.x (#108)
by Marc
04:10 queued 02:22
created

EnumSet.php$0 ➔ doCountBin()   B

Complexity

Conditions 11

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 17
cts 17
cp 1
c 0
b 0
f 0
cc 11
crap 11
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace MabeEnum;
6
7
use Countable;
8
use InvalidArgumentException;
9
use Iterator;
10
use IteratorAggregate;
11
12
/**
13
 * A set of enumerators of the given enumeration (EnumSet<T>)
14
 * based on an integer or binary bitset depending of given enumeration size.
15
 *
16
 * @copyright 2019 Marc Bennewitz
17
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
18
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
19
 */
3 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
20
class EnumSet implements IteratorAggregate, Countable
21
{
22
    /**
23
     * The classname of the Enumeration
24
     * @var string
25
     */
26
    private $enumeration;
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "enumeration" must contain a leading underscore
Loading history...
27
28
    /**
29
     * Number of enumerators defined in the enumeration
30
     * @var int
31
     */
32
    private $enumerationCount;
1 ignored issue
show
Coding Style introduced by
Private member variable "enumerationCount" must contain a leading underscore
Loading history...
33
34
    /**
35
     * Integer or binary (little endian) bitset
36
     * @var int|string
37
     */
38
    private $bitset = 0;
1 ignored issue
show
Coding Style introduced by
Private member variable "bitset" must contain a leading underscore
Loading history...
39
40
    /**#@+
41
     * Defines private method names to be called depended of how the bitset type was set too.
42
     * ... Integer or binary bitset.
43
     * ... *Int or *Bin method
44
     * 
45
     * @var string
46
     */
47
    private $fnDoCount             = 'doCountInt';
1 ignored issue
show
Coding Style introduced by
Private member variable "fnDoCount" must contain a leading underscore
Loading history...
48
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoGetOrdinals" must contain a leading underscore
Loading history...
49
    private $fnDoGetBit            = 'doGetBitInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoGetBit" must contain a leading underscore
Loading history...
50
    private $fnDoSetBit            = 'doSetBitInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoSetBit" must contain a leading underscore
Loading history...
51
    private $fnDoUnsetBit          = 'doUnsetBitInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoUnsetBit" must contain a leading underscore
Loading history...
52
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoGetBinaryBitsetLe" must contain a leading underscore
Loading history...
53
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "fnDoSetBinaryBitsetLe" must contain a leading underscore
Loading history...
54
    /**#@-*/
55
56
    /**
57
     * Constructor
58
     *
59
     * @param string $enumeration The classname of the enumeration
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
60
     * @throws InvalidArgumentException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
61
     */
62 61
    public function __construct(string $enumeration)
63
    {
64 61
        if (!\is_subclass_of($enumeration, Enum::class)) {
65 1
            throw new InvalidArgumentException(\sprintf(
66 1
                '%s can handle subclasses of %s only',
67 1
                __METHOD__,
68 1
                Enum::class
69
            ));
70
        }
71
72 60
        $this->enumeration      = $enumeration;
73 60
        $this->enumerationCount = \count($enumeration::getConstants());
74
75
        // By default the bitset is initialized as integer bitset
76
        // in case the enumeraton has more enumerators then integer bits
77
        // we will switch this into a binary bitset
78 60
        if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
79
            // init binary bitset with zeros
80 14
            $this->bitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));
81
82
            // switch internal binary bitset functions
83 14
            $this->fnDoCount             = 'doCountBin';
84 14
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
85 14
            $this->fnDoGetBit            = 'doGetBitBin';
86 14
            $this->fnDoSetBit            = 'doSetBitBin';
87 14
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
88 14
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
89 14
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
90
        }
91 60
    }
92
93
    /**
94
     * Get the classname of the enumeration
95
     * @return string
96
     */
97 1
    public function getEnumeration(): string
98
    {
99 1
        return $this->enumeration;
100
    }
101
102
    /**
103
     * Attach a new enumerator or overwrite an existing one
104
     * @param Enum|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
105
     * @return void
106
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
107
     */
108 38
    public function attach($enumerator): void
109
    {
110 38
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
111 38
    }
112
113
    /**
114
     * Detach the given enumerator
115
     * @param Enum|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
116
     * @return void
117
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
118
     */
119 7
    public function detach($enumerator): void
120
    {
121 7
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
122 7
    }
123
124
    /**
125
     * Test if the given enumerator was attached
126
     * @param Enum|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "Enum|null|boolean|integer|float|string|array" but found "Enum|null|bool|int|float|string|array" for parameter type
Loading history...
127
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
128
     */
129 8
    public function contains($enumerator): bool
130
    {
131 8
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
132
    }
133
134
    /* IteratorAggregate */
135
136
    /**
137
     * Create and return a new iterator
138
     * @return Iterator
139
     */
140
    public function getIterator(): Iterator
141
    {
142
        return new class($this, $this->enumeration, $this->enumerationCount, $this->bitset) implements Iterator {
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 113 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
143
            /**
144
             * @var EnumSet
145
             */
146
            private $enumSet;
147
148
            /**
149
             * @var string
150
             */
151
            private $enumeration;
152
153
            /**
154
             * @var int
155
             */
156
            private $enumerationCount;
157
158
            /**
159
             * @var int|string
160
             */
161
            private $bitset;
162
163
            /**
164
             * @var int
165
             */
166
            private $ordinal = 0;
167
168
            /**
169
             * @var string
170
             */
171
            private $fnDoRewind = 'doRewindInt';
172
173
            /**
174
             * Constructor.
175
             * @param EnumSet    $enumSet
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
176
             * @param string     $enumeration
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
177
             * @param int        $enumerationCount
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
178
             * @param int|string $bitset
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "integer|string" but found "int|string" for parameter type
Loading history...
179
             */
180 13
            public function __construct(EnumSet $enumSet, $enumeration, $enumerationCount, &$bitset)
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $enumeration
Loading history...
Coding Style introduced by
Type hint "int" missing for $enumerationCount
Loading history...
181
            {
182 13
                $this->enumSet          = $enumSet;
183 13
                $this->enumeration      = $enumeration;
184 13
                $this->enumerationCount = $enumerationCount;
185 13
                $this->bitset           = &$bitset;
186
187
                // By default the bitset is initialized as integer bitset
188
                // in case the enumeraton has more enumerators then integer bits
189
                // we will switch this into a binary bitset
190 13
                if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
191 4
                    $this->fnDoRewind = 'doRewindBin';
192
                }
193 13
            }
194
195
            /**
196
             * Get the current enumerator
197
             * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
198
             */
199 8
            public function current(): ?Enum
200
            {
201 8
                if ($this->valid()) {
202 8
                    return ($this->enumeration)::byOrdinal($this->ordinal);
203
                }
204
205 2
                return null;
206
            }
207
208
            /**
209
             * Get the ordinal number of the current iterator position
210
             * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
211
             */
212 8
            public function key(): int
213
            {
214 8
                return $this->ordinal;
215
            }
216
217
            /**
218
             * Go to the next valid iterator position.
219
             * If no valid iterator position is found the iterator position will be the last possible + 1.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 106 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
220
             * @return void
221
             */
222 12
            public function next(): void
223
            {
224
                do {
225 12
                    if (++$this->ordinal >= $this->enumerationCount) {
226 7
                        $this->ordinal = $this->enumerationCount;
227 7
                        return;
228
                    }
229 12
                } while (!$this->enumSet->getBit($this->ordinal));
230 12
            }
231
232
            /**
233
             * Go to the first valid iterator position.
234
             * If no valid iterator position was found the iterator position will be 0.
235
             * @return void
236
             * @uses doRewindBin()
237
             * @uses doRewindInt()
238
             */
239 10
            public function rewind(): void
240
            {
241 10
                $this->{$this->fnDoRewind}();
242 10
            }
243
244
            /**
245
             * Go to the first valid iterator position.
246
             * If no valid iterator position was found the iterator position will be 0.
247
             *
248
             * This is the binary bitset implementation.
249
             *
250
             * @return void
251
             * @see rewind()
252
             * @see doRewindInt()
253
             */
254 2
            private function doRewindBin(): void
255
            {
256 2
                if (\ltrim($this->bitset, "\0") !== '') {
257 2
                    $this->ordinal = -1;
258 2
                    $this->next();
259
                } else {
260 1
                    $this->ordinal = 0;
261
                }
262 2
            }
263
264
            /**
265
             * Go to the first valid iterator position.
266
             * If no valid iterator position was found the iterator position will be 0.
267
             *
268
             * This is the integer bitset implementation.
269
             *
270
             * @return void
271
             * @see rewind()
272
             * @see doRewindBin()
273
             */
274 8
            private function doRewindInt(): void
275
            {
276 8
                if ($this->bitset) {
277 7
                    $this->ordinal = -1;
278 7
                    $this->next();
279
                } else {
280 2
                    $this->ordinal = 0;
281
                }
282 8
            }
283
284
            /**
285
             * Test if the current iterator position is valid
286
             * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
287
             */
288 11
            public function valid(): bool
289
            {
290 11
                return $this->ordinal !== $this->enumerationCount && $this->enumSet->getBit($this->ordinal);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 108 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
291
            }
292
        };
293
    }
294
295
    /* Countable */
296
297
    /**
298
     * Count the number of elements
299
     *
300
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
301
     * @uses doCountBin()
302
     * @uses doCountInt()
303
     */
304 13
    public function count(): int
305
    {
306 13
        return $this->{$this->fnDoCount}();
307
    }
308
309
    /**
310
     * Count the number of elements.
311
     *
312
     * This is the binary bitset implementation.
313
     *
314
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
315
     * @see count()
316
     * @see doCountInt()
317
     */
318 5
    private function doCountBin(): int
319
    {
320 5
        $count   = 0;
321 5
        $bitset  = $this->bitset;
322 5
        $byteLen = \strlen($bitset);
323 5
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
324 5
            if ($bitset[$bytePos] === "\0") {
325
                // fast skip null byte
326 5
                continue;
327
            }
328
329 5
            $ord = \ord($bitset[$bytePos]);
330 5
            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...
331 5
            if ($ord & 0b00000010) ++$count;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
332 5
            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...
333 5
            if ($ord & 0b00001000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
334 5
            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...
335 5
            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...
336 5
            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...
337 5
            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...
338
        }
339 5
        return $count;
340
    }
341
342
    /**
343
     * Count the number of elements.
344
     *
345
     * This is the integer bitset implementation.
346
     *
347
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
348
     * @see count()
349
     * @see doCountBin()
350
     */
351 8
    private function doCountInt(): int
352
    {
353 8
        $count  = 0;
354 8
        $bitset = $this->bitset;
355
356
        // PHP does not support right shift unsigned
357 8
        if ($bitset < 0) {
358 2
            $count  = 1;
359 2
            $bitset = $bitset & \PHP_INT_MAX;
360
        }
361
362
        // iterate byte by byte and count set bits
363 8
        $phpIntBitSize = \PHP_INT_SIZE * 8;
364 8
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
365 8
            $bitChk = 0xff << $bitPos;
366 8
            $byte = $bitset & $bitChk;
367 8
            if ($byte) {
368 6
                $byte = $byte >> $bitPos;
369 6
                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...
370 6
                if ($byte & 0b00000010) ++$count;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
371 6
                if ($byte & 0b00000100) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
372 6
                if ($byte & 0b00001000) ++$count;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
373 6
                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...
374 6
                if ($byte & 0b00100000) ++$count;
1 ignored issue
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
375 6
                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...
376 6
                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...
377
            }
378
379 8
            if ($bitset <= $bitChk) {
380 7
                break;
381
            }
382
        }
383
384 8
        return $count;
385
    }
386
387
    /**
388
     * Check if this EnumSet is the same as other
389
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
390
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
391
     */
392 3
    public function isEqual(EnumSet $other): bool
393
    {
394 3
        return $this->enumeration === $other->enumeration
395 3
            && $this->bitset === $other->bitset;
396
    }
397
398
    /**
399
     * Check if this EnumSet is a subset of other
400
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
401
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
402
     */
403 4
    public function isSubset(EnumSet $other): bool
404
    {
405 4
        return $this->enumeration === $other->enumeration
406 4
            && ($this->bitset & $other->bitset) === $this->bitset;
407
    }
408
409
    /**
410
     * Check if this EnumSet is a superset of other
411
     * @param EnumSet $other
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
412
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
413
     */
414 4
    public function isSuperset(EnumSet $other): bool
415
    {
416 4
        return $this->enumeration === $other->enumeration
417 4
            && ($this->bitset | $other->bitset) === $this->bitset;
418
    }
419
420
    /**
421
     * Produce a new set with enumerators from both this and other (this | other)
422
     *
423
     * @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...
424
     * @return EnumSet
425
     * @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...
426
     */
427 2
    public function union(EnumSet $other): EnumSet
428
    {
429 2
        if ($this->enumeration !== $other->enumeration) {
430 1
            throw new InvalidArgumentException(\sprintf(
431 1
                'Other should be of the same enumeration as this %s',
432 1
                $this->enumeration
433
            ));
434
        }
435
436 1
        $clone = clone $this;
437 1
        $clone->bitset = $this->bitset | $other->bitset;
438 1
        return $clone;
439
    }
440
441
    /**
442
     * Produce a new set with enumerators common to both this and other (this & other)
443
     *
444
     * @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...
445
     * @return EnumSet
446
     * @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...
447
     */
448 2
    public function intersect(EnumSet $other): EnumSet
449
    {
450 2
        if ($this->enumeration !== $other->enumeration) {
451 1
            throw new InvalidArgumentException(\sprintf(
452 1
                'Other should be of the same enumeration as this %s',
453 1
                $this->enumeration
454
            ));
455
        }
456
457 1
        $clone = clone $this;
458 1
        $clone->bitset = $this->bitset & $other->bitset;
459 1
        return $clone;
460
    }
461
462
    /**
463
     * Produce a new set with enumerators in this but not in other (this - other)
464
     *
465
     * @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...
466
     * @return EnumSet
467
     * @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...
468
     */
469 2
    public function diff(EnumSet $other): EnumSet
470
    {
471 2
        if ($this->enumeration !== $other->enumeration) {
472 1
            throw new InvalidArgumentException(\sprintf(
473 1
                'Other should be of the same enumeration as this %s',
474 1
                $this->enumeration
475
            ));
476
        }
477
478 1
        $clone = clone $this;
479 1
        $clone->bitset = $this->bitset & ~$other->bitset;
480 1
        return $clone;
481
    }
482
483
    /**
484
     * Produce a new set with enumerators in either this and other but not in both (this ^ other)
485
     *
486
     * @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...
487
     * @return EnumSet
488
     * @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...
489
     */
490 2
    public function symDiff(EnumSet $other): EnumSet
491
    {
492 2
        if ($this->enumeration !== $other->enumeration) {
493 1
            throw new InvalidArgumentException(\sprintf(
494 1
                'Other should be of the same enumeration as this %s',
495 1
                $this->enumeration
496
            ));
497
        }
498
499 1
        $clone = clone $this;
500 1
        $clone->bitset = $this->bitset ^ $other->bitset;
501 1
        return $clone;
502
    }
503
504
    /**
505
     * Get ordinal numbers of the defined enumerators as array
506
     * @return int[]
507
     * @uses  doGetOrdinalsBin()
508
     * @uses  doGetOrdinalsInt()
509
     */
510 9
    public function getOrdinals(): array
511
    {
512 9
        return $this->{$this->fnDoGetOrdinals}();
513
    }
514
515
    /**
516
     * Get ordinal numbers of the defined enumerators as array.
517
     *
518
     * This is the binary bitset implementation.
519
     *
520
     * @return int[]
521
     * @see getOrdinals()
522
     * @see goGetOrdinalsInt()
523
     */
524 1
    private function doGetOrdinalsBin(): array
525
    {
526 1
        $ordinals = [];
527 1
        $bitset   = $this->bitset;
528 1
        $byteLen  = \strlen($bitset);
529 1
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
530 1
            if ($bitset[$bytePos] === "\0") {
531
                // fast skip null byte
532 1
                continue;
533
            }
534
535 1
            $ord = \ord($bitset[$bytePos]);
536 1
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
537 1
                if ($ord & (1 << $bitPos)) {
538 1
                    $ordinals[] = $bytePos * 8 + $bitPos;
539
                }
540
            }
541
        }
542 1
        return $ordinals;
543
    }
544
545
    /**
546
     * Get ordinal numbers of the defined enumerators as array.
547
     *
548
     * This is the integer bitset implementation.
549
     *
550
     * @return int[]
551
     * @see getOrdinals()
552
     * @see doGetOrdinalsBin()
553
     */
554 8
    private function doGetOrdinalsInt(): array
555
    {
556 8
        $ordinals   = [];
557 8
        $ordinalMax = $this->enumerationCount;
558 8
        $bitset     = $this->bitset;
559 8
        for ($ord = 0; $ord < $ordinalMax; ++$ord) {
560 8
            if ($bitset & (1 << $ord)) {
561 8
                $ordinals[] = $ord;
562
            }
563
        }
564 8
        return $ordinals;
565
    }
566
567
    /**
568
     * Get values of the defined enumerators as array
569
     * @return mixed[]
570
     */
571 5
    public function getValues(): array
572
    {
573 5
        $enumeration = $this->enumeration;
574 5
        $values      = [];
575 5
        foreach ($this->getOrdinals() as $ord) {
576 5
            $values[] = $enumeration::byOrdinal($ord)->getValue();
577
        }
578 5
        return $values;
579
    }
580
581
    /**
582
     * Get names of the defined enumerators as array
583
     * @return string[]
584
     */
585 1
    public function getNames(): array
586
    {
587 1
        $enumeration = $this->enumeration;
588 1
        $names       = [];
589 1
        foreach ($this->getOrdinals() as $ord) {
590 1
            $names[] = $enumeration::byOrdinal($ord)->getName();
591
        }
592 1
        return $names;
593
    }
594
595
    /**
596
     * Get the defined enumerators as array
597
     * @return Enum[]
598
     */
599 1
    public function getEnumerators(): array
600
    {
601 1
        $enumeration = $this->enumeration;
602 1
        $enumerators = [];
603 1
        foreach ($this->getOrdinals() as $ord) {
604 1
            $enumerators[] = $enumeration::byOrdinal($ord);
605
        }
606 1
        return $enumerators;
607
    }
608
609
    /**
610
     * Get binary bitset in little-endian order
611
     * 
612
     * @return string
613
     * @uses doGetBinaryBitsetLeBin()
614
     * @uses doGetBinaryBitsetLeInt()
615
     */
616 6
    public function getBinaryBitsetLe(): string
617
    {
618 6
        return $this->{$this->fnDoGetBinaryBitsetLe}();
619
    }
620
621
    /**
622
     * Get binary bitset in little-endian order.
623
     *
624
     * This is the binary bitset implementation.
625
     *
626
     * @return string
627
     * @see getBinaryBitsetLe()
628
     * @see doGetBinaryBitsetLeInt()
629
     */
630 4
    private function doGetBinaryBitsetLeBin(): string
631
    {
632 4
        return $this->bitset;
633
    }
634
635
    /**
636
     * Get binary bitset in little-endian order.
637
     *
638
     * This is the integer bitset implementation.
639
     *
640
     * @return string
641
     * @see getBinaryBitsetLe()
642
     * @see doGetBinaryBitsetLeBin()
643
     */
644 2
    private function doGetBinaryBitsetLeInt(): string
645
    {
646 2
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
647 2
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
648
    }
649
650
    /**
651
     * Set binary bitset in little-endian order
652
     *
653
     * NOTE: It resets the current position of the iterator
654
     * 
655
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
656
     * @return void
657
     * @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...
658
     * @uses doSetBinaryBitsetLeBin()
659
     * @uses doSetBinaryBitsetLeInt()
660
     */
661 12
    public function setBinaryBitsetLe(string $bitset): void
662
    {
663 12
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
664 6
    }
665
666
    /**
667
     * Set binary bitset in little-endian order
668
     *
669
     * NOTE: It resets the current position of the iterator
670
     *
671
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
672
     * @return void
673
     * @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...
674
     * @see setBinaryBitsetLeBin()
675
     * @see doSetBinaryBitsetLeInt()
676
     */
677 7
    private function doSetBinaryBitsetLeBin(string $bitset): void
678
    {
679 7
        $size   = \strlen($this->bitset);
680 7
        $sizeIn = \strlen($bitset);
681
682 7
        if ($sizeIn < $size) {
683
            // add "\0" if the given bitset is not long enough
684 1
            $bitset .= \str_repeat("\0", $size - $sizeIn);
685 6
        } elseif ($sizeIn > $size) {
686 2
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
687 1
                throw new InvalidArgumentException('out-of-range bits detected');
688
            }
689 1
            $bitset = \substr($bitset, 0, $size);
690
        }
691
692
        // truncate out-of-range bits of last byte
693 6
        $lastByteMaxOrd = $this->enumerationCount % 8;
694 6
        if ($lastByteMaxOrd !== 0) {
695 6
            $lastByte         = $bitset[-1];
696 6
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
697 6
            if ($lastByte !== $lastByteExpected) {
698 2
                throw new InvalidArgumentException('out-of-range bits detected');
699
            }
700
701 4
            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
702
        }
703
704 4
        $this->bitset = $bitset;
705 4
    }
706
707
    /**
708
     * Set binary bitset in little-endian order
709
     *
710
     * NOTE: It resets the current position of the iterator
711
     *
712
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
713
     * @return void
714
     * @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...
715
     * @see setBinaryBitsetLeBin()
716
     * @see doSetBinaryBitsetLeBin()
717
     */
718 5
    private function doSetBinaryBitsetLeInt(string $bitset): void
719
    {
720 5
        $len = \strlen($bitset);
721 5
        $int = 0;
722 5
        for ($i = 0; $i < $len; ++$i) {
723 5
            $ord = \ord($bitset[$i]);
724
725 5
            if ($ord && $i > \PHP_INT_SIZE - 1) {
726 1
                throw new InvalidArgumentException('out-of-range bits detected');
727
            }
728
729 5
            $int |= $ord << (8 * $i);
730
        }
731
732 4
        if ($int & (~0 << $this->enumerationCount)) {
733 2
            throw new InvalidArgumentException('out-of-range bits detected');
734
        }
735
736 2
        $this->bitset = $int;
737 2
    }
738
739
    /**
740
     * Get binary bitset in big-endian order
741
     * 
742
     * @return string
743
     */
744 1
    public function getBinaryBitsetBe(): string
745
    {
746 1
        return \strrev($this->bitset);
747
    }
748
749
    /**
750
     * Set binary bitset in big-endian order
751
     *
752
     * NOTE: It resets the current position of the iterator
753
     * 
754
     * @param string $bitset
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
755
     * @return void
756
     * @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...
757
     */
758 1
    public function setBinaryBitsetBe(string $bitset): void
759
    {
760 1
        $this->setBinaryBitsetLe(\strrev($bitset));
761 1
    }
762
763
    /**
764
     * Get a bit at the given ordinal number
765
     *
766
     * @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...
767
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
768
     * @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...
769
     * @uses doGetBitBin()
770
     * @uses doGetBitInt()
771
     */
772 16
    public function getBit(int $ordinal): bool
773
    {
774 16
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
775 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...
776
        }
777
778 15
        return $this->{$this->fnDoGetBit}($ordinal);
779
    }
780
781
    /**
782
     * Get a bit at the given ordinal number.
783
     *
784
     * This is the binary bitset implementation.
785
     *
786
     * @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...
787
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
788
     * @see getBit()
789
     * @see doGetBitInt()
790
     */
791 7
    private function doGetBitBin(int $ordinal): bool
792
    {
793 7
        return (\ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
794
    }
795
796
    /**
797
     * Get a bit at the given ordinal number.
798
     *
799
     * This is the integer bitset implementation.
800
     * 
801
     * @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...
802
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
803
     * @see getBit()
804
     * @see doGetBitBin()
805
     */
806 13
    private function doGetBitInt(int $ordinal): bool
807
    {
808 13
        return (bool)($this->bitset & (1 << $ordinal));
809
    }
810
811
    /**
812
     * Set a bit at the given ordinal number
813
     *
814
     * @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...
815
     * @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...
816
     * @return void
817
     * @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...
818
     * @uses doSetBitBin()
819
     * @uses doSetBitInt()
820
     * @uses doUnsetBitBin()
821
     * @uses doUnsetBitInt()
822
     */
823 2
    public function setBit(int $ordinal, bool $bit): void
824
    {
825 2
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
826 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...
827
        }
828
829 1
        if ($bit) {
830 1
            $this->{$this->fnDoSetBit}($ordinal);
831
        } else {
832 1
            $this->{$this->fnDoUnsetBit}($ordinal);
833
        }
834 1
    }
835
836
    /**
837
     * Set a bit at the given ordinal number.
838
     *
839
     * This is the binary bitset implementation.
840
     * 
841
     * @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...
842
     * @return void
843
     * @see setBit()
844
     * @see doSetBitInt()
845
     */
846 7
    private function doSetBitBin(int $ordinal): void
847
    {
848 7
        $byte = (int) ($ordinal / 8);
849 7
        $this->bitset[$byte] = $this->bitset[$byte] | \chr(1 << ($ordinal % 8));
850 7
    }
851
852
    /**
853
     * Set a bit at the given ordinal number.
854
     *
855
     * This is the binary bitset implementation.
856
     *
857
     * @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...
858
     * @return void
859
     * @see setBit()
860
     * @see doSetBitBin()
861
     */
862 32
    private function doSetBitInt(int $ordinal): void
863
    {
864 32
        $this->bitset = $this->bitset | (1 << $ordinal);
865 32
    }
866
867
    /**
868
     * Unset a bit at the given ordinal number.
869
     *
870
     * This is the binary bitset implementation.
871
     *
872
     * @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...
873
     * @return void
874
     * @see setBit()
875
     * @see doUnsetBitInt()
876
     */
877 3
    private function doUnsetBitBin(int $ordinal): void
878
    {
879 3
        $byte = (int) ($ordinal / 8);
880 3
        $this->bitset[$byte] = $this->bitset[$byte] & \chr(~(1 << ($ordinal % 8)));
881 3
    }
882
883
    /**
884
     * Unset a bit at the given ordinal number.
885
     *
886
     * This is the integer bitset implementation.
887
     *
888
     * @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...
889
     * @return void
890
     * @see setBit()
891
     * @see doUnsetBitBin()
892
     */
893 5
    private function doUnsetBitInt(int $ordinal): void
894
    {
895 5
        $this->bitset = $this->bitset & ~(1 << $ordinal);
896 5
    }
897
}
898