Completed
Pull Request — master (#63)
by Marc
04:04 queued 02:02
created

EnumSet::detach()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace MabeEnum;
4
5
use Iterator;
6
use Countable;
7
use InvalidArgumentException;
8
9
/**
10
 * EnumSet implementation in base of an integer bit set
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
     * BitSet of all attached enumerations in little endian
26
     * @var string
27
     */
28
    private $bitset;
29
30
    /**
31
     * Ordinal number of current iterator position
32
     * @var int
33
     */
34
    private $ordinal = 0;
35
36
    /**
37
     * Highest possible ordinal number
38
     * @var int
39
     */
40
    private $ordinalMax;
41
42
    /**
43
     * Constructor
44
     *
45
     * @param string $enumeration The classname of the enumeration
46
     * @throws InvalidArgumentException
47
     */
48 19
    public function __construct($enumeration)
49
    {
50 19
        if (!is_subclass_of($enumeration, __NAMESPACE__ . '\Enum')) {
51 1
            throw new InvalidArgumentException(sprintf(
52 1
                "This EnumSet can handle subclasses of '%s' only",
53
                __NAMESPACE__ . '\Enum'
54 1
            ));
55
        }
56
        
57 18
        $this->enumeration = $enumeration;
58 18
        $this->ordinalMax  = count($enumeration::getConstants());
59
        
60
        // init the bitset with zeros
61 18
        $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
62 18
    }
63
64
    /**
65
     * Get the classname of enumeration this set is for
66
     * @return string
67
     * @deprecated Please use getEnumeration() instead
68
     */
69
    public function getEnumClass()
70
    {
71
        return $this->getEnumeration();
72
    }
73
74
    /**
75
     * Get the classname of the enumeration
76
     * @return string
77
     */
78 2
    public function getEnumeration()
79
    {
80 2
        return $this->enumeration;
81
    }
82
83
    /**
84
     * Attach a new enumerator or overwrite an existing one
85
     * @param Enum|null|boolean|int|float|string $enumerator
86
     * @return void
87
     * @throws InvalidArgumentException On an invalid given enumerator
88
     */
89 11
    public function attach($enumerator)
90
    {
91 11
        $enumeration = $this->enumeration;
92 11
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
93 11
    }
94
95
    /**
96
     * Detach the given enumerator
97
     * @param Enum|null|boolean|int|float|string $enumerator
98
     * @return void
99
     * @throws InvalidArgumentException On an invalid given enumerator
100
     */
101 5
    public function detach($enumerator)
102
    {
103 5
        $enumeration = $this->enumeration;
104 5
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
105 5
    }
106
107
    /**
108
     * Test if the given enumerator was attached
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return boolean
111
     */
112 7
    public function contains($enumerator)
113
    {
114 7
        $enumeration = $this->enumeration;
115 7
        return $this->getBit($enumeration::get($enumerator)->getOrdinal());
116
    }
117
118
    /* Iterator */
119
120
    /**
121
     * Get the current enumerator
122
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
123
     */
124 5
    public function current()
125
    {
126 5
        if ($this->valid()) {
127 5
            $enumeration = $this->enumeration;
128 5
            return $enumeration::getByOrdinal($this->ordinal);
129
        }
130
131 2
        return null;
132
    }
133
134
    /**
135
     * Get the ordinal number of the current iterator position
136
     * @return int
137
     */
138 6
    public function key()
139
    {
140 6
        return $this->ordinal;
141
    }
142
143
    /**
144
     * Go to the next valid iterator position.
145
     * If no valid iterator position is found the iterator position will be the last possible + 1.
146
     * @return void
147
     */
148 10
    public function next()
149
    {
150
        do {
151 10
            if (++$this->ordinal >= $this->ordinalMax) {
152 4
                $this->ordinal = $this->ordinalMax;
153 4
                return;
154
            }
155 10
        } while (!$this->getBit($this->ordinal));
156 10
    }
157
158
    /**
159
     * Go to the first valid iterator position.
160
     * If no valid iterator position in found the iterator position will be 0.
161
     * @return void
162
     */
163 8
    public function rewind()
164
    {
165 8
        if (trim($this->bitset, "\0") !== '') {
166 8
            $this->ordinal = -1;
167 8
            $this->next();
168 8
        } else {
169 1
            $this->ordinal = 0;
170
        }
171 8
    }
172
173
    /**
174
     * Test if the iterator in a valid state
175
     * @return boolean
176
     */
177 6
    public function valid()
178
    {
179 6
        return $this->ordinal !== $this->ordinalMax && $this->getBit($this->ordinal);
180
    }
181
182
    /* Countable */
183
184
    /**
185
     * Count the number of elements
186
     * @return int
187
     */
188 7
    public function count()
189
    {
190 7
        $cnt = 0;
191 7
        $ord = 0;
192
193 7
        while ($ord !== $this->ordinalMax) {
194 6
            if ($this->getBit($ord++)) {
195 6
                ++$cnt;
196 6
            }
197 6
        }
198
199 7
        return $cnt;
200
    }
201
202
    /**
203
     * Get binary bitset in little-endian order
204
     * 
205
     * @return string
206
     */
207 3
    public function getBinaryBitsetLe()
208
    {
209 3
        return $this->bitset;
210
    }
211
212
    /**
213
     * Set binary bitset in little-endian order
214
     *
215
     * NOTE: It resets the current position of the iterator
216
     * 
217
     * @param string $bitset
218
     * @return void
219
     * @throws InvalidArgumentException On a non string is given as Parameter
220 4
     */
221
    public function setBinaryBitsetLe($bitset)
222 4
    {
223 1
        if (!is_string($bitset)) {
224
            throw new InvalidArgumentException('Bitset must be a string');
225
        }
226 3
        
227 3
        $size   = ceil($this->ordinalMax / 8);
228 3
        $sizeIn = strlen($bitset);
229
        
230 3
        if ($sizeIn < $size) {
231
            // add "\0" if the given bitset is not long enough
232 1
            $bitset .= str_repeat("\0", $size - $sizeIn);
233 3
        } elseif ($sizeIn > $size) {
234 1
            $bitset = substr($bitset, 0, $size);
235 1
        }
236
        
237 3
        $this->bitset = $bitset;
238
        
239 3
        $this->rewind();
240 3
    }
241
242
    /**
243
     * Get binary bitset in big-endian order
244
     * 
245
     * @return string
246
     */
247
    public function getBinaryBitsetBe()
248 14
    {
249
        return strrev($this->bitset);
250 14
    }
251
252
    /**
253
     * Set binary bitset in big-endian order
254
     *
255
     * NOTE: It resets the current position of the iterator
256
     * 
257
     * @param string $bitset
258
     * @return void
259 11
     * @throws InvalidArgumentException On a non string is given as Parameter
260
     */
261 11
    public function setBinaryBitsetBe($bitset)
262 11
    {
263 11
        if (!is_string($bitset)) {
264
            throw new InvalidArgumentException('Bitset must be a string');
265
        }
266
        
267
	$this->setBinaryBitsetLe(strrev($bitset));
268
    }
269
270
    /**
271 5
     * Get the binary bitset
272
     * 
273 5
     * @return string Returns the binary bitset in big-endian order
274 5
     * @deprecated Please use getBinaryBitsetBe() instead
275 5
     */
276
    public function getBitset()
277
    {
278
        return $this->getBinaryBitsetBe();
279
    }
280
281
    /**
282
     * Set the bitset.
283
     * NOTE: It resets the current position of the iterator
284
     * 
285
     * @param string $bitset The binary bitset in big-endian order
286
     * @return void
287
     * @throws InvalidArgumentException On a non string is given as Parameter
288
     * @deprecated Please use setBinaryBitsetBe() instead
289
     */
290
    public function setBitset($bitset)
291
    {
292
        $this->setBinaryBitsetBE($bitset);
293
    }
294
295
    /**
296
     * Get a bit at the given ordinal number
297
     * 
298
     * @param $ordinal int Ordinal number of bit to get
299
     * @return boolean
300
     */
301
    private function getBit($ordinal)
302
    {
303
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
304
    }
305
306
    /**
307
     * Set a bit at the given ordinal number
308
     * 
309
     * @param $ordinal int Ordnal number of bit to set
310
     * @return void
311
     */
312
    private function setBit($ordinal)
313
    {
314
        $byte = (int) ($ordinal / 8);
315
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
316
    }
317
318
    /**
319
     * Unset a bit at the given ordinal number
320
     * 
321
     * @param $ordinal int Ordinal number of bit to unset
322
     * @return void
323
     */
324
    private function unsetBit($ordinal)
325
    {
326
        $byte = (int) ($ordinal / 8);
327
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
328
    }
329
}
330