Completed
Push — feature/74 ( acf369 )
by Marc
01:25
created

AbstractEnumSet::next()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 9
loc 9
ccs 0
cts 9
cp 0
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 0
crap 12
1
<?php
2
3
namespace MabeEnum;
4
5
use Iterator;
6
use Countable;
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
abstract class AbstractEnumSet implements Iterator, Countable
17
{
18
    /**
19
     * The classname of the Enumeration
20
     * @var string
21
     */
22
    protected $enumeration;
23
24
    /**
25
     * Ordinal number of current iterator position
26
     * @var int
27
     */
28
    protected $ordinal = 0;
29
30
    /**
31
     * Highest possible ordinal number
32
     * @var int
33
     */
34
    protected $ordinalMax;
35
36
    /**
37
     * Constructor
38
     *
39
     * @param string $enumeration The classname of the enumeration
40
     * @throws InvalidArgumentException
41
     */
42
    public function __construct($enumeration)
43
    {
44 View Code Duplication
        if (!is_subclass_of($enumeration, Enum::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \MabeEnum\Enum::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
Duplication introduced by
This code seems to be duplicated across 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...
45
            throw new InvalidArgumentException(sprintf(
46
                "%s can handle subclasses of '%s' only",
47
                static::class,
48
                Enum::class
49
            ));
50
        }
51
        
52
        $this->enumeration = $enumeration;
53
        $this->ordinalMax  = count($enumeration::getConstants());
54
    }
55
56
    /**
57
     * Get the classname of the enumeration
58
     * @return string
59
     */
60
    public function getEnumeration()
61
    {
62
        return $this->enumeration;
63
    }
64
65
    /**
66
     * Attach a new enumerator or overwrite an existing one
67
     * @param Enum|null|boolean|int|float|string $enumerator
68
     * @return void
69
     * @throws InvalidArgumentException On an invalid given enumerator
70
     */
71
    public function attach($enumerator)
72
    {
73
        $enumeration = $this->enumeration;
74
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
75
    }
76
77
    /**
78
     * Detach the given enumerator
79
     * @param Enum|null|boolean|int|float|string $enumerator
80
     * @return void
81
     * @throws InvalidArgumentException On an invalid given enumerator
82
     */
83
    public function detach($enumerator)
84
    {
85
        $enumeration = $this->enumeration;
86
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
87
    }
88
89
    /**
90
     * Test if the given enumerator was attached
91
     * @param Enum|null|boolean|int|float|string $enumerator
92
     * @return boolean
93
     */
94
    public function contains($enumerator)
95
    {
96
        $enumeration = $this->enumeration;
97
        return $this->getBit($enumeration::get($enumerator)->getOrdinal());
98
    }
99
100
    /* Iterator */
101
102
    /**
103
     * Get the current enumerator
104
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
105
     */
106
    public function current()
107
    {
108
        if ($this->valid()) {
109
            $enumeration = $this->enumeration;
110
            return $enumeration::byOrdinal($this->ordinal);
111
        }
112
113
        return null;
114
    }
115
116
    /**
117
     * Get the ordinal number of the current iterator position
118
     * @return int
119
     */
120
    public function key()
121
    {
122
        return $this->ordinal;
123
    }
124
125
    /**
126
     * Go to the next valid iterator position.
127
     * If no valid iterator position is found the iterator position will be the last possible + 1.
128
     * @return void
129
     */
130 View Code Duplication
    public function next()
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...
131
    {
132
        do {
133
            if (++$this->ordinal >= $this->ordinalMax) {
134
                $this->ordinal = $this->ordinalMax;
135
                return;
136
            }
137
        } while (!$this->getBit($this->ordinal));
138
    }
139
140
    /**
141
     * Go to the first valid iterator position.
142
     * If no valid iterator position in found the iterator position will be 0.
143
     * @return void
144
     */
145 View Code Duplication
    public function rewind()
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...
146
    {
147
        if (trim($this->bitset, "\0") !== '') {
0 ignored issues
show
Bug introduced by
The property bitset does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
148
            $this->ordinal = -1;
149
            $this->next();
150
        } else {
151
            $this->ordinal = 0;
152
        }
153
    }
154
155
    /**
156
     * Test if the iterator in a valid state
157
     * @return boolean
158
     */
159
    public function valid()
160
    {
161
        return $this->ordinal !== $this->ordinalMax && $this->getBit($this->ordinal);
162
    }
163
164
    /* Countable */
165
166
    /**
167
     * Count the number of elements
168
     * @return int
169
     */
170 View Code Duplication
    public function count()
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...
171
    {
172
        $count   = 0;
173
        $byteLen = strlen($this->bitset);
174
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
175
            if ($this->bitset[$bytePos] === "\0") {
176
                continue; // fast skip null byte
177
            }
178
179
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
180
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
181
                    ++$count;
182
                }
183
            }
184
        }
185
186
        return $count;
187
    }
188
189
    /**
190
     * Check if this EnumSet is the same as other
191
     * @param EnumSet $other
192
     * @return bool
193
     */
194
    public function isEqual(EnumSet $other)
195
    {
196
        return $this->enumeration === $other->enumeration
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
197
            && $this->bitset === $other->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
198
    }
199
200
    /**
201
     * Check if this EnumSet is a subset of other
202
     * @param EnumSet $other
203
     * @return bool
204
     */
205 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...
206
    {
207
        if ($this->enumeration !== $other->enumeration) {
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
208
            return false;
209
        }
210
211
        return ($this->bitset & $other->bitset) === $this->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
212
    }
213
214
    /**
215
     * Check if this EnumSet is a superset of other
216
     * @param EnumSet $other
217
     * @return bool
218
     */
219 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...
220
    {
221
        if ($this->enumeration !== $other->enumeration) {
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
222
            return false;
223
        }
224
225
        return ($this->bitset | $other->bitset) === $this->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
226
    }
227
228
    /**
229
     * Produce a new set with enumerators from both this and other (this | other)
230
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
231
     * @return EnumSet
232
     */
233 View Code Duplication
    public function union(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
234
    {
235
        $bitset = $this->bitset;
236
        foreach (func_get_args() as $other) {
237
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
238
                throw new InvalidArgumentException(sprintf(
239
                    "Others should be an instance of %s of the same enumeration as this %s",
240
                    __CLASS__,
241
                    $this->enumeration
242
                ));
243
            }
244
245
            $bitset |= $other->bitset;
246
        }
247
248
        $clone = clone $this;
249
        $clone->bitset = $bitset;
250
        return $clone;
251
    }
252
253
    /**
254
     * Produce a new set with enumerators common to both this and other (this & other)
255
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
256
     * @return EnumSet
257
     */
258 View Code Duplication
    public function intersect(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
259
    {
260
        $bitset = $this->bitset;
261
        foreach (func_get_args() as $other) {
262
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
263
                throw new InvalidArgumentException(sprintf(
264
                    "Others should be an instance of %s of the same enumeration as this %s",
265
                    __CLASS__,
266
                    $this->enumeration
267
                ));
268
            }
269
270
            $bitset &= $other->bitset;
271
        }
272
273
        $clone = clone $this;
274
        $clone->bitset = $bitset;
275
        return $clone;
276
    }
277
278
    /**
279
     * Produce a new set with enumerators in this but not in other (this - other)
280
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
281
     * @return EnumSet
282
     */
283 View Code Duplication
    public function diff(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
284
    {
285
        $bitset = '';
286
        foreach (func_get_args() as $other) {
287
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
288
                throw new InvalidArgumentException(sprintf(
289
                    "Others should be an instance of %s of the same enumeration as this %s",
290
                    __CLASS__,
291
                    $this->enumeration
292
                ));
293
            }
294
295
            $bitset |= $other->bitset;
296
        }
297
298
        $clone = clone $this;
299
        $clone->bitset = $this->bitset & ~$bitset;
300
        return $clone;
301
    }
302
303
    /**
304
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
305
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
306
     * @return EnumSet
307
     */
308 View Code Duplication
    public function symDiff(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
309
    {
310
        $bitset = '';
311
        foreach (func_get_args() as $other) {
312
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
313
                throw new InvalidArgumentException(sprintf(
314
                    "Others should be an instance of %s of the same enumeration as this %s",
315
                    __CLASS__,
316
                    $this->enumeration
317
                ));
318
            }
319
320
            $bitset |= $other->bitset;
321
        }
322
323
        $clone = clone $this;
324
        $clone->bitset = $this->bitset ^ $bitset;
325
        return $clone;
326
    }
327
328
    /**
329
     * Get ordinal numbers of the defined enumerators as array
330
     * @return int[]
331
     */
332 View Code Duplication
    public function getOrdinals()
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...
333
    {
334
        $ordinals = array();
335
        $byteLen  = strlen($this->bitset);
336
337
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
338
            if ($this->bitset[$bytePos] === "\0") {
339
                continue; // fast skip null byte
340
            }
341
342
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
343
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
344
                    $ordinals[] = $bytePos * 8 + $bitPos;
345
                }
346
            }
347
        }
348
349
        return $ordinals;
350
    }
351
352
    /**
353
     * Get values of the defined enumerators as array
354
     * @return null[]|bool[]|int[]|float[]|string[]
355
     */
356 View Code Duplication
    public function getValues()
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...
357
    {
358
        $enumeration = $this->enumeration;
359
        $values      = array();
360
        foreach ($this->getOrdinals() as $ord) {
361
            $values[] = $enumeration::byOrdinal($ord)->getValue();
362
        }
363
        return $values;
364
    }
365
366
    /**
367
     * Get names of the defined enumerators as array
368
     * @return string[]
369
     */
370 View Code Duplication
    public function getNames()
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...
371
    {
372
        $enumeration = $this->enumeration;
373
        $names       = array();
374
        foreach ($this->getOrdinals() as $ord) {
375
            $names[] = $enumeration::byOrdinal($ord)->getName();
376
        }
377
        return $names;
378
    }
379
380
    /**
381
     * Get the defined enumerators as array
382
     * @return Enum[]
383
     */
384 View Code Duplication
    public function getEnumerators()
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...
385
    {
386
        $enumeration = $this->enumeration;
387
        $enumerators = array();
388
        foreach ($this->getOrdinals() as $ord) {
389
            $enumerators[] = $enumeration::byOrdinal($ord);
390
        }
391
        return $enumerators;
392
    }
393
394
    /**
395
     * Get binary bitset in little-endian order
396
     * 
397
     * @return string
398
     */
399
    public function getBinaryBitsetLe()
400
    {
401
        return $this->bitset;
402
    }
403
404
    /**
405
     * Set binary bitset in little-endian order
406
     *
407
     * NOTE: It resets the current position of the iterator
408
     * 
409
     * @param string $bitset
410
     * @return void
411
     * @throws InvalidArgumentException On a non string is given as Parameter
412
     */
413 View Code Duplication
    public function setBinaryBitsetLe($bitset)
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...
414
    {
415
        if (!is_string($bitset)) {
416
            throw new InvalidArgumentException('Bitset must be a string');
417
        }
418
419
        $size   = strlen($this->bitset);
420
        $sizeIn = strlen($bitset);
421
422
        if ($sizeIn < $size) {
423
            // add "\0" if the given bitset is not long enough
424
            $bitset .= str_repeat("\0", $size - $sizeIn);
425
        } elseif ($sizeIn > $size) {
426
            if (trim(substr($bitset, $size), "\0") !== '') {
427
                throw new InvalidArgumentException('Out-Of-Range bits detected');
428
            }
429
            $bitset = substr($bitset, 0, $size);
430
        }
431
432
        // truncate out-of-range bits of last byte
433
        $lastByteMaxOrd = $this->ordinalMax % 8;
434
        if ($lastByteMaxOrd !== 0) {
435
            $lastByte         = $bitset[$size - 1];
436
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
437
            if ($lastByte !== $lastByteExpected) {
438
                throw new InvalidArgumentException('Out-Of-Range bits detected');
439
            }
440
441
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
442
        }
443
444
        $this->bitset = $bitset;
445
446
        // reset the iterator position
447
        $this->rewind();
448
    }
449
450
    /**
451
     * Get binary bitset in big-endian order
452
     * 
453
     * @return string
454
     */
455
    public function getBinaryBitsetBe()
456
    {
457
        return strrev($this->bitset);
458
    }
459
460
    /**
461
     * Set binary bitset in big-endian order
462
     *
463
     * NOTE: It resets the current position of the iterator
464
     * 
465
     * @param string $bitset
466
     * @return void
467
     * @throws InvalidArgumentException On a non string is given as Parameter
468
     */
469
    public function setBinaryBitsetBe($bitset)
470
    {
471
        if (!is_string($bitset)) {
472
            throw new InvalidArgumentException('Bitset must be a string');
473
        }
474
        $this->setBinaryBitsetLe(strrev($bitset));
475
    }
476
477
    /**
478
     * Get a bit at the given ordinal number
479
     * 
480
     * @param $ordinal int Ordinal number of bit to get
481
     * @return boolean
482
     */
483
    private function getBit($ordinal)
484
    {
485
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
486
    }
487
488
    /**
489
     * Set a bit at the given ordinal number
490
     * 
491
     * @param $ordinal int Ordnal number of bit to set
492
     * @return void
493
     */
494
    private function setBit($ordinal)
495
    {
496
        $byte = (int) ($ordinal / 8);
497
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
498
    }
499
500
    /**
501
     * Unset a bit at the given ordinal number
502
     * 
503
     * @param $ordinal int Ordinal number of bit to unset
504
     * @return void
505
     */
506
    private function unsetBit($ordinal)
507
    {
508
        $byte = (int) ($ordinal / 8);
509
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
510
    }
511
}
512