Completed
Push — feature/74 ( 60c27e...f551d9 )
by Marc
04:30
created

EnumSet   D

Complexity

Total Complexity 97

Size/Duplication

Total Lines 817
Duplicated Lines 11.75 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 88.81%

Importance

Changes 16
Bugs 6 Features 2
Metric Value
wmc 97
c 16
b 6
f 2
lcom 1
cbo 0
dl 96
loc 817
ccs 254
cts 286
cp 0.8881
rs 4

44 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 3
A getEnumeration() 0 4 1
A attach() 0 5 1
A detach() 0 5 1
A contains() 0 5 1
A current() 0 9 2
A key() 0 4 1
A next() 0 9 3
A rewind() 0 4 1
A doRewindBin() 0 9 2
A doRewindInt() 0 9 2
A valid() 0 4 2
A count() 0 4 1
B doCountBin() 0 19 5
A doCountInt() 0 11 3
A isEqual() 0 5 2
A isSubset() 8 8 2
A isSuperset() 8 8 2
A union() 18 18 3
A intersect() 18 18 3
B diff() 22 22 4
B symDiff() 22 22 4
A getOrdinals() 0 4 1
B doGetOrdinalsBin() 0 19 5
A doGetOrdinalsInt() 0 10 3
A getValues() 0 9 2
A getNames() 0 9 2
A getEnumerators() 0 9 2
A getBinaryBitsetLe() 0 4 1
A doGetBinaryBitsetLeBin() 0 4 1
A doGetBinaryBitsetLeInt() 0 5 2
A setBinaryBitsetLe() 0 11 2
B doSetBinaryBitsetLeBin() 0 29 6
B doSetBinaryBitsetLeInt() 0 20 5
A getBinaryBitsetBe() 0 4 1
A setBinaryBitsetBe() 0 7 2
A getBit() 0 8 3
A doGetBitBin() 0 4 1
A doGetBitInt() 0 4 1
A setBit() 0 12 4
A doSetBitBin() 0 5 1
A doSetBitInt() 0 4 1
A doUnsetBitBin() 0 5 1
A doUnsetBitInt() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EnumSet often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EnumSet, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace MabeEnum;
4
5
use Countable;
6
use Iterator;
7
use InvalidArgumentException;
8
9
/**
10
 * This EnumSet is based on a bitset of a binary string.
11
 *
12
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
 * @copyright Copyright (c) 2015 Marc Bennewitz
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
16
class EnumSet implements Iterator, Countable
17
{
18
    /**
19
     * The classname of the Enumeration
20
     * @var string
21
     */
22
    private $enumeration;
23
24
    /**
25
     * Ordinal number of current iterator position
26
     * @var int
27
     */
28
    private $ordinal = 0;
29
30
    /**
31
     * Highest possible ordinal number
32
     * @var int
33
     */
34
    private $ordinalMax;
35
36
    /**
37
     * Integer or binary (little endian) bitset
38
     * @var int|string
39
     */
40
    private $bitset = 0;
41
42
    /**#@+
43
     * Defined the private method names to be called depended of
44
     * how the bitset type was set too.
45
     * ... Integer or binary bitset.
46
     * ... *Int or *Bin method
47
     * 
48
     * @var string
49
     */
50
    private $fnDoRewind            = 'doRewindInt';
51
    private $fnDoCount             = 'doCountInt';
52
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';
53
    private $fnDoGetBit            = 'doGetBitInt';
54
    private $fnDoSetBit            = 'doSetBitInt';
55
    private $fnDoUnsetBit          = 'doUnsetBitInt';
56
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
57
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
58
    /**#@-*/
59
60
    /**
61
     * Constructor
62
     *
63
     * @param string $enumeration The classname of the enumeration
64
     * @throws InvalidArgumentException
65
     */
66 100
    public function __construct($enumeration)
67
    {
68 100
        if (!is_subclass_of($enumeration, Enum::class)) {
69 2
            throw new InvalidArgumentException(sprintf(
70 2
                "%s can handle subclasses of '%s' only",
71 2
                static::class,
72 1
                Enum::class
73 1
            ));
74
        }
75
76 98
        $this->enumeration = $enumeration;
77 98
        $this->ordinalMax  = count($enumeration::getConstants());
78
79
        // By default the bitset is initialized as integer bitset
80
        // in case the enumeraton has more enumerators then integer bits
81
        // we will switch this into a binary bitset
82 98
        if ($this->ordinalMax > PHP_INT_SIZE * 8) {
83
            // init binary bitset with zeros
84 18
            $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
85
86
            // switch internal binary bitset functions
87 18
            $this->fnDoRewind            = 'doRewindBin';
88 18
            $this->fnDoCount             = 'doCountBin';
89 18
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
90 18
            $this->fnDoGetBit            = 'doGetBitBin';
91 18
            $this->fnDoSetBit            = 'doSetBitBin';
92 18
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
93 18
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
94 18
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
95 9
        }
96 98
    }
97
98
    /**
99
     * Get the classname of the enumeration
100
     * @return string
101
     */
102 2
    public function getEnumeration()
103
    {
104 2
        return $this->enumeration;
105
    }
106
107
    /**
108
     * Attach a new enumerator or overwrite an existing one
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return void
111
     * @throws InvalidArgumentException On an invalid given enumerator
112
     */
113 68
    public function attach($enumerator)
114
    {
115 68
        $enumeration = $this->enumeration;
116 68
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
117 68
    }
118
119
    /**
120
     * Detach the given enumerator
121
     * @param Enum|null|boolean|int|float|string $enumerator
122
     * @return void
123
     * @throws InvalidArgumentException On an invalid given enumerator
124
     */
125 12
    public function detach($enumerator)
126
    {
127 12
        $enumeration = $this->enumeration;
128 12
        $this->{$this->fnDoUnsetBit}($enumeration::get($enumerator)->getOrdinal());
129 12
    }
130
131
    /**
132
     * Test if the given enumerator was attached
133
     * @param Enum|null|boolean|int|float|string $enumerator
134
     * @return boolean
135
     */
136 18
    public function contains($enumerator)
137
    {
138 18
        $enumeration = $this->enumeration;
139 18
        return $this->{$this->fnDoGetBit}($enumeration::get($enumerator)->getOrdinal());
140
    }
141
142
    /* Iterator */
143
144
    /**
145
     * Get the current enumerator
146
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
147
     */
148 18
    public function current()
149
    {
150 18
        if ($this->valid()) {
151 18
            $enumeration = $this->enumeration;
152 18
            return $enumeration::byOrdinal($this->ordinal);
153
        }
154
155 4
        return null;
156
    }
157
158
    /**
159
     * Get the ordinal number of the current iterator position
160
     * @return int
161
     */
162 12
    public function key()
163
    {
164 12
        return $this->ordinal;
165
    }
166
167
    /**
168
     * Go to the next valid iterator position.
169
     * If no valid iterator position is found the iterator position will be the last possible + 1.
170
     * @return void
171
     */
172 30
    public function next()
173
    {
174
        do {
175 30
            if (++$this->ordinal >= $this->ordinalMax) {
176 8
                $this->ordinal = $this->ordinalMax;
177 8
                return;
178
            }
179 30
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
180 30
    }
181
182
    /**
183
     * Go to the first valid iterator position.
184
     * If no valid iterator position was found the iterator position will be 0.
185
     * @return void
186
     * @see doRewindBin
187
     * @see doRewindInt
188
     */
189 18
    public function rewind()
190
    {
191 18
        $this->{$this->fnDoRewind}();
192 18
    }
193
194
    /**
195
     * Go to the first valid iterator position.
196
     * If no valid iterator position was found the iterator position will be 0.
197
     *
198
     * This is the binary bitset implementation.
199
     *
200
     * @return void
201
     * @see rewind
202
     * @see doRewindInt
203
     */
204 6
    private function doRewindBin()
205
    {
206 6
        if (trim($this->bitset, "\0") !== '') {
207 6
            $this->ordinal = -1;
208 6
            $this->next();
209 3
        } else {
210
            $this->ordinal = 0;
211
        }
212 6
    }
213
214
    /**
215
     * Go to the first valid iterator position.
216
     * If no valid iterator position was found the iterator position will be 0.
217
     *
218
     * This is the binary bitset implementation.
219
     *
220
     * @return void
221
     * @see rewind
222
     * @see doRewindBin
223
     */
224 12
    private function doRewindInt()
225
    {
226 12
        if ($this->bitset) {
227 12
            $this->ordinal = -1;
228 12
            $this->next();
229 6
        } else {
230 2
            $this->ordinal = 0;
231
        }
232 12
    }
233
234
    /**
235
     * Test if the iterator in a valid state
236
     * @return boolean
237
     */
238 20
    public function valid()
239
    {
240 20
        return $this->ordinal !== $this->ordinalMax && $this->{$this->fnDoGetBit}($this->ordinal);
241
    }
242
243
    /* Countable */
244
245
    /**
246
     * Count the number of elements
247
     *
248
     * @return int
249
     * @see doCountBin
250
     * @see doCountInt
251
     */
252 18
    public function count()
253
    {
254 18
        return $this->{$this->fnDoCount}();
255
    }
256
257
    /**
258
     * Count the number of elements.
259
     *
260
     * This is the binary bitset implementation.
261
     *
262
     * @return int
263
     * @see count
264
     * @see doCountInt
265
     */
266 8
    private function doCountBin()
267
    {
268 8
        $count = 0;
269 8
        $byteLen = strlen($this->bitset);
270 8
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
271 8
            if ($this->bitset[$bytePos] === "\0") {
272
                // fast skip null byte
273 8
                continue;
274
            }
275
276 8
            $ord = ord($this->bitset[$bytePos]);
277 8
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
278 8
                if ($ord & (1 << $bitPos)) {
279 8
                    ++$count;
280 4
                }
281 4
            }
282 4
        }
283 8
        return $count;
284
    }
285
286
    /**
287
     * Count the number of elements.
288
     *
289
     * This is the integer bitset implementation.
290
     *
291
     * @return int
292
     * @see count
293
     * @see doCountBin
294
     */
295 10
    private function doCountInt()
296
    {
297 10
        $count = 0;
298 10
        $ord = 0;
299 10
        while ($ord !== $this->ordinalMax) {
300 8
            if ($this->bitset & (1 << $ord++)) {
301 8
                ++$count;
302 4
            }
303 4
        }
304 10
        return $count;
305
    }
306
307
    /**
308
     * Check if this EnumSet is the same as other
309
     * @param EnumSet $other
310
     * @return bool
311
     */
312 6
    public function isEqual(EnumSet $other)
313
    {
314 6
        return $this->enumeration === $other->enumeration
315 6
            && $this->bitset === $other->bitset;
316
    }
317
318
    /**
319
     * Check if this EnumSet is a subset of other
320
     * @param EnumSet $other
321
     * @return bool
322
     */
323 8 View Code Duplication
    public function isSubset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
    {
325 8
        if ($this->enumeration !== $other->enumeration) {
326 2
            return false;
327
        }
328
329 6
        return ($this->bitset & $other->bitset) === $this->bitset;
330
    }
331
332
    /**
333
     * Check if this EnumSet is a superset of other
334
     * @param EnumSet $other
335
     * @return bool
336
     */
337 8 View Code Duplication
    public function isSuperset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338
    {
339 8
        if ($this->enumeration !== $other->enumeration) {
340 2
            return false;
341
        }
342
343 6
        return ($this->bitset | $other->bitset) === $this->bitset;
344
    }
345
346
    /**
347
     * Produce a new set with enumerators from both this and other (this | other)
348
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
349
     * @return EnumSet
350
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
351 4 View Code Duplication
    public function union(EnumSet ...$others)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
    {
353 4
        $bitset = $this->bitset;
354 4
        foreach ($others as $other) {
355 4
            if ($this->enumeration !== $other->enumeration) {
356 2
                throw new InvalidArgumentException(sprintf(
357 2
                    'Others should be of the same enumeration as this %s',
358 2
                    $this->enumeration
359 1
                ));
360
            }
361
362 2
            $bitset |= $other->bitset;
363 1
        }
364
365 2
        $clone = clone $this;
366 2
        $clone->bitset = $bitset;
367 2
        return $clone;
368
    }
369
370
    /**
371
     * Produce a new set with enumerators common to both this and other (this & other)
372
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
373
     * @return EnumSet
374
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
375 4 View Code Duplication
    public function intersect(EnumSet ...$others)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
    {
377 4
        $bitset = $this->bitset;
378 4
        foreach ($others as $other) {
379 4
            if ($this->enumeration !== $other->enumeration) {
380 2
                throw new InvalidArgumentException(sprintf(
381 2
                    'Others should be of the same enumeration as this %s',
382 2
                    $this->enumeration
383 1
                ));
384
            }
385
386 2
            $bitset &= $other->bitset;
387 1
        }
388
389 2
        $clone = clone $this;
390 2
        $clone->bitset = $bitset;
391 2
        return $clone;
392
    }
393
394
    /**
395
     * Produce a new set with enumerators in this but not in other (this - other)
396
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
397
     * @return EnumSet
398
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
399 4 View Code Duplication
    public function diff(EnumSet ...$others)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
    {
401 4
        $clone = clone $this;
402
403 4
        if (isset($others[0])) {
404 4
            $bitset = $others[0]->bitset;
405 4
            foreach ($others as $other) {
406 4
                if ($this->enumeration !== $other->enumeration) {
407 2
                    throw new InvalidArgumentException(sprintf(
408 2
                        'Others should of the same enumeration as this %s',
409 2
                        $this->enumeration
410 1
                    ));
411
                }
412
413 2
                $bitset |= $other->bitset;
414 1
            }
415
416 2
            $clone->bitset = $this->bitset & ~$bitset;
417 1
        }
418
419 2
        return $clone;
420
    }
421
422
    /**
423
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
424
     * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union
425
     * @return EnumSet
426
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $others not be EnumSet[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
427 4 View Code Duplication
    public function symDiff(EnumSet ...$others)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
    {
429 4
        $clone = clone $this;
430
431 4
        if (isset($others[0])) {
432 4
            $bitset = $others[0]->bitset;
433 4
            foreach ($others as $other) {
434 4
                if ($this->enumeration !== $other->enumeration) {
435 2
                    throw new InvalidArgumentException(sprintf(
436 2
                        'Others should be of the same enumeration as this %s',
437 2
                        $this->enumeration
438 1
                    ));
439
                }
440
441 2
                $bitset |= $other->bitset;
442 1
            }
443
444 2
            $clone->bitset = $this->bitset ^ $bitset;
445 1
        }
446
447 2
        return $clone;
448
    }
449
450
    /**
451
     * Get ordinal numbers of the defined enumerators as array
452
     * @return int[]
453
     */
454 24
    public function getOrdinals()
455
    {
456 24
        return $this->{$this->fnDoGetOrdinals}();
457
    }
458
459
    /**
460
     * Get ordinal numbers of the defined enumerators as array.
461
     *
462
     * This is the binary bitset implementation.
463
     *
464
     * @return int[]
465
     * @see getOrdinals
466
     * @see goGetOrdinalsInt
467
     */
468
    private function doGetOrdinalsBin()
469
    {
470
        $ordinals = [];
471
        $byteLen = strlen($this->bitset);
472
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
473
            if ($this->bitset[$bytePos] === "\0") {
474
                // fast skip null byte
475
                continue;
476
            }
477
478
            $ord = ord($this->bitset[$bytePos]);
479
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
480
                if ($ord & (1 << $bitPos)) {
481
                    $ordinals[] = $bytePos * 8 + $bitPos;
482
                }
483
            }
484
        }
485
        return $ordinals;
486
    }
487
488
    /**
489
     * Get ordinal numbers of the defined enumerators as array.
490
     *
491
     * This is the integer bitset implementation.
492
     *
493
     * @return int[]
494
     * @see getOrdinals
495
     * @see doGetOrdinalsBin
496
     */
497 24
    private function doGetOrdinalsInt()
498
    {
499 24
        $ordinals = [];
500 24
        for ($ord = 0; $ord < $this->ordinalMax; ++$ord) {
501 24
            if ($this->bitset & (1 << $ord)) {
502 24
                $ordinals[] = $ord;
503 12
            }
504 12
        }
505 24
        return $ordinals;
506
    }
507
508
    /**
509
     * Get values of the defined enumerators as array
510
     * @return null[]|bool[]|int[]|float[]|string[]
511
     */
512 12
    public function getValues()
513
    {
514 12
        $enumeration = $this->enumeration;
515 12
        $values      = [];
516 12
        foreach ($this->getOrdinals() as $ord) {
517 12
            $values[] = $enumeration::byOrdinal($ord)->getValue();
518 6
        }
519 12
        return $values;
520
    }
521
522
    /**
523
     * Get names of the defined enumerators as array
524
     * @return string[]
525
     */
526 4
    public function getNames()
527
    {
528 4
        $enumeration = $this->enumeration;
529 4
        $names       = [];
530 4
        foreach ($this->getOrdinals() as $ord) {
531 4
            $names[] = $enumeration::byOrdinal($ord)->getName();
532 2
        }
533 4
        return $names;
534
    }
535
536
    /**
537
     * Get the defined enumerators as array
538
     * @return Enum[]
539
     */
540 4
    public function getEnumerators()
541
    {
542 4
        $enumeration = $this->enumeration;
543 4
        $enumerators = [];
544 4
        foreach ($this->getOrdinals() as $ord) {
545 4
            $enumerators[] = $enumeration::byOrdinal($ord);
546 2
        }
547 4
        return $enumerators;
548
    }
549
550
    /**
551
     * Get binary bitset in little-endian order
552
     * 
553
     * @return string
554
     */
555 8
    public function getBinaryBitsetLe()
556
    {
557 8
        return $this->{$this->fnDoGetBinaryBitsetLe}();
558
    }
559
560
    /**
561
     * Get binary bitset in little-endian order.
562
     *
563
     * This is the binary bitset implementation.
564
     *
565
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
566
     */
567 6
    private function doGetBinaryBitsetLeBin()
568
    {
569 6
        return $this->bitset;
570
    }
571
572
    /**
573
     * Get binary bitset in little-endian order.
574
     *
575
     * This is the integer bitset implementation.
576
     *
577
     * @return string
578
     */
579 2
    private function doGetBinaryBitsetLeInt()
580
    {
581 2
        $bin = pack(PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
582 2
        return substr($bin, 0, ceil($this->ordinalMax / 8));
583
    }
584
585
    /**
586
     * Set binary bitset in little-endian order
587
     *
588
     * NOTE: It resets the current position of the iterator
589
     * 
590
     * @param string $bitset
591
     * @return void
592
     * @throws InvalidArgumentException On a non string is given as Parameter
593
     */
594 16
    public function setBinaryBitsetLe($bitset)
595
    {
596 16
        if (!is_string($bitset)) {
597 2
            throw new InvalidArgumentException('Bitset must be a string');
598
        }
599
600 14
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
601
602
        // reset the iterator position
603 8
        $this->rewind();
604 8
    }
605
606
    /**
607
     * Set binary bitset in little-endian order
608
     *
609
     * NOTE: It resets the current position of the iterator
610
     *
611
     * @param string $bitset
612
     * @return void
613
     * @throws InvalidArgumentException On a non string is given as Parameter
614
     */
615 8
    private function doSetBinaryBitsetLeBin($bitset)
616
    {
617 8
        $size   = strlen($this->bitset);
618 8
        $sizeIn = strlen($bitset);
619
620 8
        if ($sizeIn < $size) {
621
            // add "\0" if the given bitset is not long enough
622 2
            $bitset .= str_repeat("\0", $size - $sizeIn);
623 7
        } elseif ($sizeIn > $size) {
624
            if (trim(substr($bitset, $size), "\0") !== '') {
625
                throw new InvalidArgumentException('Out-Of-Range bits detected');
626
            }
627
            $bitset = substr($bitset, 0, $size);
628
        }
629
630
        // truncate out-of-range bits of last byte
631 8
        $lastByteMaxOrd = $this->ordinalMax % 8;
632 8
        if ($lastByteMaxOrd !== 0) {
633 8
            $lastByte         = $bitset[$size - 1];
634 8
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
635 8
            if ($lastByte !== $lastByteExpected) {
636 2
                throw new InvalidArgumentException('Out-Of-Range bits detected');
637
            }
638
639 6
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
640 3
        }
641
642 6
        $this->bitset = $bitset;
643 6
    }
644
645
    /**
646
     * Set binary bitset in little-endian order
647
     *
648
     * NOTE: It resets the current position of the iterator
649
     *
650
     * @param string $bitset
651
     * @return void
652
     * @throws InvalidArgumentException On a non string is given as Parameter
653
     */
654 6
    private function doSetBinaryBitsetLeInt($bitset)
655
    {
656 6
        $len = strlen($bitset);
657 6
        $int = 0;
658 6
        for ($i = 0; $i < $len; ++$i) {
659 6
            $ord = ord($bitset[$i]);
660
661 6
            if ($ord && $i > PHP_INT_SIZE) {
662
                throw new InvalidArgumentException('Out-Of-Range bits detected');
663
            }
664
665 6
            $int |= $ord << (8 * $i);
666 3
        }
667
668 6
        if ($int & (~0 << $this->ordinalMax)) {
669 4
            throw new InvalidArgumentException('Out-Of-Range bits detected');
670
        }
671
672 2
        $this->bitset = $int;
673 2
    }
674
675
    /**
676
     * Get binary bitset in big-endian order
677
     * 
678
     * @return string
679
     */
680 2
    public function getBinaryBitsetBe()
681
    {
682 2
        return strrev($this->bitset);
683
    }
684
685
    /**
686
     * Set binary bitset in big-endian order
687
     *
688
     * NOTE: It resets the current position of the iterator
689
     * 
690
     * @param string $bitset
691
     * @return void
692
     * @throws InvalidArgumentException On a non string is given as Parameter
693
     */
694 4
    public function setBinaryBitsetBe($bitset)
695
    {
696 4
        if (!is_string($bitset)) {
697 2
            throw new InvalidArgumentException('Bitset must be a string');
698
        }
699 2
        $this->setBinaryBitsetLe(strrev($bitset));
700 2
    }
701
702
    /**
703
     * Get a bit at the given ordinal number
704
     *
705
     * @param int $ordinal Ordinal number of bit to get
706
     * @return boolean
707
     */
708
    public function getBit($ordinal)
709
    {
710
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
711
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
0 ignored issues
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...
712
        }
713
714
        return $this->{$this->fnDoGetBit}($ordinal);
715
    }
716
717
    /**
718
     * Get a bit at the given ordinal number.
719
     *
720
     * This is the binary bitset implementation.
721
     *
722
     * @param int $ordinal Ordinal number of bit to get
723
     * @return boolean
724
     * @see getBit
725
     * @see doGetBitInt
726
     */
727 12
    private function doGetBitBin($ordinal)
728
    {
729 12
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
730
    }
731
732
    /**
733
     * Get a bit at the given ordinal number.
734
     *
735
     * This is the integer bitset implementation.
736
     * 
737
     * @param int $ordinal Ordinal number of bit to get
738
     * @return boolean
739
     * @see getBit
740
     * @see doGetBitBin
741
     */
742 26
    private function doGetBitInt($ordinal)
743
    {
744 26
        return (bool)($this->bitset & (1 << $ordinal));
745
    }
746
747
    /**
748
     * Set a bit at the given ordinal number
749
     *
750
     * @param int $ordinal Ordnal number of bit to set
751
     * @param bool $bit    The bit to set
752
     * @return void
753
     * @see doSetBitBin
754
     * @see doSetBitInt
755
     * @see doUnsetBin
756
     * @see doUnsetInt
757
     */
758
    public function setBit($ordinal, $bit)
759
    {
760
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
761
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
0 ignored issues
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...
762
        }
763
764
        if ($bit) {
765
            $this->{$this->fnDoSetBit}($ordinal);
766
        } else {
767
            $this->{$this->fnDoUnsetBit}($ordinal);
768
        }
769
    }
770
771
    /**
772
     * Set a bit at the given ordinal number.
773
     *
774
     * This is the binary bitset implementation.
775
     * 
776
     * @param int $ordinal Ordnal number of bit to set
777
     * @return void
778
     * @see setBit
779
     * @see doSetBitInt
780
     */
781 6
    private function doSetBitBin($ordinal)
782
    {
783 6
        $byte = (int) ($ordinal / 8);
784 6
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
785 6
    }
786
787
    /**
788
     * Set a bit at the given ordinal number.
789
     *
790
     * This is the binary bitset implementation.
791
     *
792
     * @param int $ordinal Ordnal number of bit to set
793
     * @return void
794
     * @see setBit
795
     * @see doSetBitBin
796
     */
797 62
    private function doSetBitInt($ordinal)
798
    {
799 62
        $this->bitset = $this->bitset | (1 << $ordinal);
800 62
    }
801
802
    /**
803
     * Unset a bit at the given ordinal number.
804
     *
805
     * This is the binary bitset implementation.
806
     *
807
     * @param int $ordinal Ordinal number of bit to unset
808
     * @return void
809
     * @see setBit
810
     * @see doUnsetBitInt
811
     */
812 4
    private function doUnsetBitBin($ordinal)
813
    {
814 4
        $byte = (int) ($ordinal / 8);
815 4
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
816 4
    }
817
818
    /**
819
     * Unset a bit at the given ordinal number.
820
     *
821
     * This is the integer bitset implementation.
822
     *
823
     * @param int $ordinal Ordinal number of bit to unset
824
     * @return void
825
     * @see setBit
826
     * @see doUnsetBitBin
827
     */
828 8
    private function doUnsetBitInt($ordinal)
829
    {
830 8
        $this->bitset = $this->bitset & ~(1 << $ordinal);
831 8
    }
832
}
833