Skip to content

Commit d392f07

Browse files
committed
EnumSet implementation should be ready
1 parent 32f146b commit d392f07

File tree

2 files changed

+305
-8
lines changed

2 files changed

+305
-8
lines changed

src/MabeEnum/EnumSet.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,27 @@
1616
*/
1717
class EnumSet implements Iterator, Countable
1818
{
19-
private $enumClass = 'EnumInheritance';
19+
/**
20+
* Flag for a unique set of enumerations
21+
*/
22+
const UNIQUE = 1;
23+
24+
/**
25+
* Flag for an ordered set of enumerations by ordinal
26+
*/
27+
const ORDERED = 2;
28+
29+
private $enumClass;
2030
private $list = array();
2131
private $index = 0;
32+
private $flags = self::UNIQUE;
2233

2334
/**
2435
* Constructor
25-
* @param string $enumClass The classname of an enumeration the map is for
36+
* @param string $enumClass The classname of an enumeration the map is for
37+
* @param null|int $flags Flags to define behaviours
2638
*/
27-
public function __construct($enumClass)
39+
public function __construct($enumClass, $flags = null)
2840
{
2941
if (!is_subclass_of($enumClass, __NAMESPACE__ . '\Enum')) {
3042
throw new InvalidArgumentException(sprintf(
@@ -33,6 +45,10 @@ public function __construct($enumClass)
3345
));
3446
}
3547
$this->enumClass = $enumClass;
48+
49+
if ($flags !== null) {
50+
$this->flags = (int) $flags;
51+
}
3652
}
3753

3854
/**
@@ -44,6 +60,15 @@ public function getEnumClass()
4460
return $this->enumClass;
4561
}
4662

63+
/**
64+
* Get flags of defined behaviours
65+
* @return int
66+
*/
67+
public function getFlags()
68+
{
69+
return $this->flags;
70+
}
71+
4772
/**
4873
* Attach a new enumeration or overwrite an existing one
4974
* @param Enum|scalar $enum
@@ -55,8 +80,13 @@ public function attach($enum, $data = null)
5580
{
5681
$this->initEnum($enum);
5782
$ordinal = $enum->getOrdinal();
58-
if (!in_array($ordinal, $this->list, true)) {
83+
84+
if (!($this->flags & self::UNIQUE) || !in_array($ordinal, $this->list, true)) {
5985
$this->list[] = $ordinal;
86+
87+
if ($this->flags & self::ORDERED) {
88+
sort($this->list);
89+
}
6090
}
6191
}
6292

@@ -72,18 +102,21 @@ public function contains($enum)
72102
}
73103

74104
/**
75-
* Detach an enumeration
105+
* Detach all enumerations same as the given enum
76106
* @param Enum|scalar $enum
77107
* @return void
78108
* @throws InvalidArgumentException On an invalid given enum
79109
*/
80110
public function detach($enum)
81111
{
82112
$this->initEnum($enum);
83-
$index = array_search($enum->getOrdinal(), $this->list, true);
84-
if ($index) {
113+
114+
while (($index = array_search($enum->getOrdinal(), $this->list, true)) !== false) {
85115
unset($this->list[$index]);
86116
}
117+
118+
// reset index positions to have a real list
119+
$this->list = array_values($this->list);
87120
}
88121

89122
/* Iterator */
@@ -94,7 +127,7 @@ public function detach($enum)
94127
*/
95128
public function current()
96129
{
97-
if (!$this->valid()) {
130+
if (!isset($this->list[$this->index])) {
98131
return null;
99132
}
100133

tests/MabeEnumTest/EnumSet.php

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
<?php
2+
3+
namespace MabeEnumTest;
4+
5+
use MabeEnum\Enum;
6+
use MabeEnum\EnumSet;
7+
use MabeEnumTest\TestAsset\EnumWithoutDefaultValue;
8+
use MabeEnumTest\TestAsset\EnumInheritance;
9+
use PHPUnit_Framework_TestCase as TestCase;
10+
11+
/**
12+
* Unit tests for the class MabeEnum\EnumSet
13+
*
14+
* @link http://github.com/marc-mabe/php-enum for the canonical source repository
15+
* @copyright Copyright (c) 2012 Marc Bennewitz
16+
* @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
17+
*/
18+
class EnumSetTest extends TestCase
19+
{
20+
public function testBasic()
21+
{
22+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue');
23+
$this->assertSame('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', $enumSet->getEnumClass());
24+
25+
$enum1 = EnumWithoutDefaultValue::ONE();
26+
$enum2 = EnumWithoutDefaultValue::TWO();
27+
28+
$this->assertFalse($enumSet->contains($enum1));
29+
$this->assertNull($enumSet->attach($enum1));
30+
$this->assertTrue($enumSet->contains($enum1));
31+
32+
$this->assertFalse($enumSet->contains($enum2));
33+
$this->assertNull($enumSet->attach($enum2));
34+
$this->assertTrue($enumSet->contains($enum2));
35+
36+
$this->assertNull($enumSet->detach($enum1));
37+
$this->assertFalse($enumSet->contains($enum1));
38+
39+
$this->assertNull($enumSet->detach($enum2));
40+
$this->assertFalse($enumSet->contains($enum2));
41+
}
42+
43+
public function testBasicWithConstantValuesAsEnums()
44+
{
45+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue');
46+
47+
$enum1 = EnumWithoutDefaultValue::ONE;
48+
$enum2 = EnumWithoutDefaultValue::TWO;
49+
50+
$this->assertFalse($enumSet->contains($enum1));
51+
$this->assertNull($enumSet->attach($enum1));
52+
$this->assertTrue($enumSet->contains($enum1));
53+
54+
$this->assertFalse($enumSet->contains($enum2));
55+
$this->assertNull($enumSet->attach($enum2));
56+
$this->assertTrue($enumSet->contains($enum2));
57+
58+
$this->assertNull($enumSet->detach($enum1));
59+
$this->assertFalse($enumSet->contains($enum1));
60+
61+
$this->assertNull($enumSet->detach($enum2));
62+
$this->assertFalse($enumSet->contains($enum2));
63+
}
64+
65+
public function testUnique()
66+
{
67+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', EnumSet::UNIQUE);
68+
69+
$enumSet->attach(EnumWithoutDefaultValue::ONE());
70+
$enumSet->attach(EnumWithoutDefaultValue::ONE);
71+
72+
$enumSet->attach(EnumWithoutDefaultValue::TWO());
73+
$enumSet->attach(EnumWithoutDefaultValue::TWO);
74+
75+
$this->assertSame(2, $enumSet->count());
76+
}
77+
78+
public function testNotUnique()
79+
{
80+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', 0);
81+
82+
$enumSet->attach(EnumWithoutDefaultValue::ONE());
83+
$enumSet->attach(EnumWithoutDefaultValue::ONE);
84+
85+
$enumSet->attach(EnumWithoutDefaultValue::TWO());
86+
$enumSet->attach(EnumWithoutDefaultValue::TWO);
87+
88+
$this->assertSame(4, $enumSet->count());
89+
90+
// detch remove all
91+
$enumSet->detach(EnumWithoutDefaultValue::ONE);
92+
$this->assertSame(2, $enumSet->count());
93+
}
94+
95+
public function testIterateUnordered()
96+
{
97+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', EnumSet::UNIQUE);
98+
99+
$enum1 = EnumWithoutDefaultValue::ONE();
100+
$enum2 = EnumWithoutDefaultValue::TWO();
101+
102+
// an empty enum set needs to be invalid, starting by 0
103+
$this->assertSame(0, $enumSet->count());
104+
$this->assertFalse($enumSet->valid());
105+
106+
// attach
107+
$enumSet->attach($enum1);
108+
$enumSet->attach($enum2);
109+
110+
// a not empty enum map should be valid, starting by 0 (if not iterated)
111+
$this->assertSame(2, $enumSet->count());
112+
$this->assertTrue($enumSet->valid());
113+
$this->assertSame(0, $enumSet->key());
114+
$this->assertSame($enum1, $enumSet->current());
115+
116+
// go to the next element (last)
117+
$this->assertNull($enumSet->next());
118+
$this->assertTrue($enumSet->valid());
119+
$this->assertSame(1, $enumSet->key());
120+
$this->assertSame($enum2, $enumSet->current());
121+
122+
// go to the next element (out of range)
123+
$this->assertNull($enumSet->next());
124+
$this->assertFalse($enumSet->valid());
125+
$this->assertSame(2, $enumSet->key());
126+
127+
// rewind will set the iterator position back to 0
128+
$enumSet->rewind();
129+
$this->assertTrue($enumSet->valid());
130+
$this->assertSame(0, $enumSet->key());
131+
$this->assertSame($enum1, $enumSet->current());
132+
}
133+
134+
public function testIterateOrdered()
135+
{
136+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', EnumSet::UNIQUE | EnumSet::ORDERED);
137+
138+
$enum1 = EnumWithoutDefaultValue::ONE();
139+
$enum2 = EnumWithoutDefaultValue::TWO();
140+
141+
// an empty enum set needs to be invalid, starting by 0
142+
$this->assertSame(0, $enumSet->count());
143+
$this->assertFalse($enumSet->valid());
144+
145+
// attach
146+
$enumSet->attach($enum2);
147+
$enumSet->attach($enum1);
148+
149+
// a not empty enum map should be valid, starting by 0 (if not iterated)
150+
$this->assertSame(2, $enumSet->count());
151+
$this->assertTrue($enumSet->valid());
152+
$this->assertSame(0, $enumSet->key());
153+
$this->assertSame($enum1, $enumSet->current());
154+
155+
// go to the next element (last)
156+
$this->assertNull($enumSet->next());
157+
$this->assertTrue($enumSet->valid());
158+
$this->assertSame(1, $enumSet->key());
159+
$this->assertSame($enum2, $enumSet->current());
160+
161+
// go to the next element (out of range)
162+
$this->assertNull($enumSet->next());
163+
$this->assertFalse($enumSet->valid());
164+
$this->assertSame(2, $enumSet->key());
165+
166+
// rewind will set the iterator position back to 0
167+
$enumSet->rewind();
168+
$this->assertTrue($enumSet->valid());
169+
$this->assertSame(0, $enumSet->key());
170+
$this->assertSame($enum1, $enumSet->current());
171+
}
172+
173+
public function testIterateOrderedNotUnique()
174+
{
175+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue', EnumSet::ORDERED);
176+
177+
$enum1 = EnumWithoutDefaultValue::ONE();
178+
$enum2 = EnumWithoutDefaultValue::TWO();
179+
180+
// an empty enum set needs to be invalid, starting by 0
181+
$this->assertSame(0, $enumSet->count());
182+
$this->assertFalse($enumSet->valid());
183+
184+
// attach
185+
$enumSet->attach($enum2);
186+
$enumSet->attach($enum1);
187+
$enumSet->attach($enum2);
188+
$enumSet->attach($enum1);
189+
190+
// index 0
191+
$this->assertSame(4, $enumSet->count());
192+
$this->assertTrue($enumSet->valid());
193+
$this->assertSame(0, $enumSet->key());
194+
$this->assertSame($enum1, $enumSet->current());
195+
196+
// index 1
197+
$this->assertNull($enumSet->next());
198+
$this->assertTrue($enumSet->valid());
199+
$this->assertSame(1, $enumSet->key());
200+
$this->assertSame($enum1, $enumSet->current());
201+
202+
// index 2
203+
$this->assertNull($enumSet->next());
204+
$this->assertTrue($enumSet->valid());
205+
$this->assertSame(2, $enumSet->key());
206+
$this->assertSame($enum2, $enumSet->current());
207+
208+
// index 3 (last)
209+
$this->assertNull($enumSet->next());
210+
$this->assertTrue($enumSet->valid());
211+
$this->assertSame(3, $enumSet->key());
212+
$this->assertSame($enum2, $enumSet->current());
213+
214+
// go to the next element (out of range)
215+
$this->assertNull($enumSet->next());
216+
$this->assertFalse($enumSet->valid());
217+
$this->assertSame(4, $enumSet->key());
218+
219+
// rewind will set the iterator position back to 0
220+
$enumSet->rewind();
221+
$this->assertTrue($enumSet->valid());
222+
$this->assertSame(0, $enumSet->key());
223+
$this->assertSame($enum1, $enumSet->current());
224+
}
225+
226+
public function testIterateAndDetach()
227+
{
228+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumInheritance');
229+
230+
$enum1 = EnumInheritance::ONE();
231+
$enum2 = EnumInheritance::TWO();
232+
$enum3 = EnumInheritance::INHERITANCE();
233+
234+
// attach
235+
$enumSet->attach($enum1);
236+
$enumSet->attach($enum2);
237+
$enumSet->attach($enum3);
238+
239+
// index 1
240+
$enumSet->next();
241+
$this->assertSame($enum2, $enumSet->current());
242+
243+
// detach enum of current index
244+
$enumSet->detach($enumSet->current());
245+
$this->assertSame($enum3, $enumSet->current());
246+
247+
// detach enum of current index if the last index
248+
$enumSet->detach($enumSet->current());
249+
$this->assertFalse($enumSet->valid());
250+
}
251+
252+
public function testConstructThrowsInvalidArgumentExceptionIfEnumClassDoesNotExtendBaseEnum()
253+
{
254+
$this->setExpectedException('InvalidArgumentException');
255+
new EnumSet('stdClass');
256+
}
257+
258+
public function testInitEnumThrowsInvalidArgumentExceptionOnInvalidEnum()
259+
{
260+
$enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumWithoutDefaultValue');
261+
$this->setExpectedException('InvalidArgumentException');
262+
$this->assertFalse($enumSet->contains(EnumInheritance::INHERITANCE()));
263+
}
264+
}

0 commit comments

Comments
 (0)