Skip to content

Commit 8b1e418

Browse files
committed
Refactor EnumMap - fixes #91
1 parent fc93d58 commit 8b1e418

File tree

5 files changed

+124
-40
lines changed

5 files changed

+124
-40
lines changed

bench/EnumMapBench.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public function init()
4848

4949
$this->emptyMap = new EnumMap(Enum66::class);
5050
$this->fullMap = new EnumMap(Enum66::class);
51-
foreach ($this->enumerators as $i => $enumerator) {
52-
$this->fullMap->offsetSet($enumerator, $i);
51+
foreach ($this->enumerators as $enumerator) {
52+
$this->fullMap->offsetSet($enumerator);
5353
}
5454
}
5555

src/Enum.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ final public static function byOrdinal($ordinal)
247247
}
248248

249249
if (!isset(self::$names[$class][$ordinal])) {
250-
throw new InvalidArgumentException(sprintf(
250+
throw new InvalidArgumentException(\sprintf(
251251
'Invalid ordinal number, must between 0 and %s',
252252
\count(self::$names[$class]) - 1
253253
));

src/EnumMap.php

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,45 @@
22

33
namespace MabeEnum;
44

5-
use SplObjectStorage;
5+
use ArrayAccess;
6+
use Countable;
67
use InvalidArgumentException;
8+
use Iterator;
9+
use UnexpectedValueException;
710

811
/**
9-
* A map of enumerator keys of the given enumeration (EnumMap<T>)
10-
* based on SplObjectStorage
12+
* A map of enumerators (EnumMap<T>) and mixed values.
1113
*
1214
* @link http://github.com/marc-mabe/php-enum for the canonical source repository
1315
* @copyright Copyright (c) 2017 Marc Bennewitz
1416
* @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
1517
*/
16-
class EnumMap extends SplObjectStorage
18+
class EnumMap implements ArrayAccess, Countable, Iterator
1719
{
1820
/**
1921
* The classname of the enumeration type
2022
* @var string
2123
*/
2224
private $enumeration;
2325

26+
/**
27+
* Internal map of ordinal number and value
28+
* @var array
29+
*/
30+
private $map = [];
31+
32+
/**
33+
* List of ordinal numbers
34+
* @var int[]
35+
*/
36+
private $ordinals = [];
37+
38+
/**
39+
* Current iterator position
40+
* @var int
41+
*/
42+
private $pos = 0;
43+
2444
/**
2545
* Constructor
2646
* @param string $enumeration The classname of the enumeration type
@@ -29,7 +49,7 @@ class EnumMap extends SplObjectStorage
2949
public function __construct($enumeration)
3050
{
3151
if (!\is_subclass_of($enumeration, Enum::class)) {
32-
throw new InvalidArgumentException(sprintf(
52+
throw new InvalidArgumentException(\sprintf(
3353
"This EnumMap can handle subclasses of '%s' only",
3454
Enum::class
3555
));
@@ -55,8 +75,7 @@ public function getEnumeration()
5575
*/
5676
public function attach($enumerator, $data = null)
5777
{
58-
$enumeration = $this->enumeration;
59-
parent::attach($enumeration::get($enumerator), $data);
78+
return $this->offsetSet($enumerator, $data);
6079
}
6180

6281
/**
@@ -66,13 +85,7 @@ public function attach($enumerator, $data = null)
6685
*/
6786
public function contains($enumerator)
6887
{
69-
try {
70-
$enumeration = $this->enumeration;
71-
return parent::contains($enumeration::get($enumerator));
72-
} catch (InvalidArgumentException $e) {
73-
// On an InvalidArgumentException the given argument can't be contained in this map
74-
return false;
75-
}
88+
return $this->offsetExists($enumerator);
7689
}
7790

7891
/**
@@ -83,8 +96,7 @@ public function contains($enumerator)
8396
*/
8497
public function detach($enumerator)
8598
{
86-
$enumeration = $this->enumeration;
87-
parent::detach($enumeration::get($enumerator));
99+
$this->offsetUnset($enumerator);
88100
}
89101

90102
/**
@@ -95,7 +107,14 @@ public function detach($enumerator)
95107
*/
96108
public function offsetExists($enumerator)
97109
{
98-
return $this->contains($enumerator);
110+
try {
111+
$enumeration = $this->enumeration;
112+
$ord = $enumeration::get($enumerator)->getOrdinal();
113+
return isset($this->map[$ord]);
114+
} catch (InvalidArgumentException $e) {
115+
// An invalid enumerator can't be contained in this map
116+
return false;
117+
}
99118
}
100119

101120
/**
@@ -107,7 +126,15 @@ public function offsetExists($enumerator)
107126
public function offsetGet($enumerator)
108127
{
109128
$enumeration = $this->enumeration;
110-
return parent::offsetGet($enumeration::get($enumerator));
129+
$ord = $enumeration::get($enumerator)->getOrdinal();
130+
if (!isset($this->map[$ord])) {
131+
throw new UnexpectedValueException(\sprintf(
132+
"Enumerator '%s' could not be found",
133+
\is_object($enumerator) ? $enumerator->getValue() : $enumerator
134+
));
135+
}
136+
137+
return $this->map[$ord];
111138
}
112139

113140
/**
@@ -121,7 +148,12 @@ public function offsetGet($enumerator)
121148
public function offsetSet($enumerator, $data = null)
122149
{
123150
$enumeration = $this->enumeration;
124-
parent::offsetSet($enumeration::get($enumerator), $data);
151+
$ord = $enumeration::get($enumerator)->getOrdinal();
152+
153+
if (!isset($this->map[$ord])) {
154+
$this->ordinals[] = $ord;
155+
}
156+
$this->map[$ord] = $data;
125157
}
126158

127159
/**
@@ -134,7 +166,11 @@ public function offsetSet($enumerator, $data = null)
134166
public function offsetUnset($enumerator)
135167
{
136168
$enumeration = $this->enumeration;
137-
parent::offsetUnset($enumeration::get($enumerator));
169+
$ord = $enumeration::get($enumerator)->getOrdinal();
170+
171+
if (($idx = \array_search($ord, $this->ordinals, true)) !== false) {
172+
unset($this->map[$ord], $this->ordinals[$idx]);
173+
}
138174
}
139175

140176
/**
@@ -143,7 +179,11 @@ public function offsetUnset($enumerator)
143179
*/
144180
public function current()
145181
{
146-
return parent::getInfo();
182+
if (!isset($this->ordinals[$this->pos])) {
183+
return null;
184+
}
185+
186+
return $this->map[$this->ordinals[$this->pos]];
147187
}
148188

149189
/**
@@ -152,6 +192,48 @@ public function current()
152192
*/
153193
public function key()
154194
{
155-
return parent::current();
195+
if (!isset($this->ordinals[$this->pos])) {
196+
return null;
197+
}
198+
199+
$enumeration = $this->enumeration;
200+
return $enumeration::byOrdinal($this->ordinals[$this->pos]);
201+
}
202+
203+
/**
204+
* Reset the iterator position to zero.
205+
* @return void
206+
*/
207+
public function rewind()
208+
{
209+
$this->pos = 0;
210+
}
211+
212+
/**
213+
* Increment the iterator position by one.
214+
* @return void
215+
*/
216+
public function next()
217+
{
218+
++$this->pos;
219+
}
220+
221+
/**
222+
* Test if the iterator is in a valid state
223+
* @return boolean
224+
*/
225+
public function valid()
226+
{
227+
return isset($this->ordinals[$this->pos]);
228+
}
229+
230+
/**
231+
* Count the number of elements
232+
*
233+
* @return int
234+
*/
235+
public function count()
236+
{
237+
return \count($this->ordinals);
156238
}
157239
}

src/EnumSet.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private function doRewindInt()
232232
}
233233

234234
/**
235-
* Test if the iterator in a valid state
235+
* Test if the iterator is in a valid state
236236
* @return boolean
237237
*/
238238
public function valid()
@@ -377,7 +377,7 @@ public function isSuperset(EnumSet $other)
377377
public function union(EnumSet $other)
378378
{
379379
if ($this->enumeration !== $other->enumeration) {
380-
throw new InvalidArgumentException(sprintf(
380+
throw new InvalidArgumentException(\sprintf(
381381
'Other should be of the same enumeration as this %s',
382382
$this->enumeration
383383
));
@@ -397,7 +397,7 @@ public function union(EnumSet $other)
397397
public function intersect(EnumSet $other)
398398
{
399399
if ($this->enumeration !== $other->enumeration) {
400-
throw new InvalidArgumentException(sprintf(
400+
throw new InvalidArgumentException(\sprintf(
401401
'Other should be of the same enumeration as this %s',
402402
$this->enumeration
403403
));
@@ -417,7 +417,7 @@ public function intersect(EnumSet $other)
417417
public function diff(EnumSet $other)
418418
{
419419
if ($this->enumeration !== $other->enumeration) {
420-
throw new InvalidArgumentException(sprintf(
420+
throw new InvalidArgumentException(\sprintf(
421421
'Other should be of the same enumeration as this %s',
422422
$this->enumeration
423423
));
@@ -437,7 +437,7 @@ public function diff(EnumSet $other)
437437
public function symDiff(EnumSet $other)
438438
{
439439
if ($this->enumeration !== $other->enumeration) {
440-
throw new InvalidArgumentException(sprintf(
440+
throw new InvalidArgumentException(\sprintf(
441441
'Other should be of the same enumeration as this %s',
442442
$this->enumeration
443443
));

tests/MabeEnumTest/EnumMapTest.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,26 @@
1919
*/
2020
class EnumMapTest extends TestCase
2121
{
22-
public function testBasic()
22+
public function testBasicWithEnumeratorInstances()
2323
{
2424
$enumMap = new EnumMap(EnumBasic::class);
2525
$this->assertSame(EnumBasic::class, $enumMap->getEnumeration());
2626

27-
$enum1 = EnumBasic::ONE();
27+
$enum1 = EnumBasic::TWO();
2828
$value1 = 'value1';
2929

30-
$enum2 = EnumBasic::TWO();
30+
$enum2 = EnumBasic::ONE();
3131
$value2 = 'value2';
3232

3333
$this->assertFalse($enumMap->contains($enum1));
3434
$this->assertNull($enumMap->attach($enum1, $value1));
3535
$this->assertTrue($enumMap->contains($enum1));
3636
$this->assertSame($value1, $enumMap[$enum1]);
37-
$this->assertSame(spl_object_hash($enum1), $enumMap->getHash($enum1));
3837

3938
$this->assertFalse($enumMap->contains($enum2));
4039
$this->assertNull($enumMap->attach($enum2, $value2));
4140
$this->assertTrue($enumMap->contains($enum2));
4241
$this->assertSame($value2, $enumMap[$enum2]);
43-
$this->assertSame(spl_object_hash($enum2), $enumMap->getHash($enum2));
4442

4543
$this->assertNull($enumMap->detach($enum1));
4644
$this->assertFalse($enumMap->contains($enum1));
@@ -49,7 +47,7 @@ public function testBasic()
4947
$this->assertFalse($enumMap->contains($enum2));
5048
}
5149

52-
public function testBasicWithConstantValuesAsEnums()
50+
public function testBasicWithEnumeratorValues()
5351
{
5452
$enumMap = new EnumMap(EnumBasic::class);
5553

@@ -186,9 +184,13 @@ public function testContainsAndOffsetExistsReturnsFalseOnInvalidEnum()
186184
public function testSerializable()
187185
{
188186
$enumMap = new EnumMap(EnumBasic::class);
189-
if ($enumMap instanceof Serializable) {
190-
$enumMap->offsetSet(EnumBasic::ONE, 'one');
191-
serialize($enumMap);
192-
}
187+
$enumMap[EnumBasic::ONE()] = 'one';
188+
189+
$enumMapCopy = unserialize(serialize($enumMap));
190+
$this->assertTrue($enumMapCopy->offsetExists(EnumBasic::ONE));
191+
$this->assertFalse($enumMapCopy->offsetExists(EnumBasic::TWO));
192+
193+
// unserialized instance should be the same
194+
$this->assertSame(EnumBasic::ONE(), $enumMapCopy->key());
193195
}
194196
}

0 commit comments

Comments
 (0)