Skip to content

Commit ae8e926

Browse files
committed
Immutable EnumMap
1 parent d55b0d8 commit ae8e926

File tree

3 files changed

+318
-59
lines changed

3 files changed

+318
-59
lines changed

src/EnumMap.php

Lines changed: 172 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use UnexpectedValueException;
1313

1414
/**
15-
* A map of enumerators (EnumMap<T>) and mixed values.
15+
* A map of enumerators and data values (EnumMap<K extends Enum, V>).
1616
*
1717
* @copyright 2019 Marc Bennewitz
1818
* @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
@@ -27,17 +27,18 @@ class EnumMap implements ArrayAccess, Countable, IteratorAggregate
2727
private $enumeration;
2828

2929
/**
30-
* Internal map of ordinal number and value
30+
* Internal map of ordinal number and data value
3131
* @var array
3232
*/
3333
private $map = [];
3434

3535
/**
3636
* Constructor
3737
* @param string $enumeration The classname of the enumeration type
38+
* @param null|iterable $map Initialize map
3839
* @throws InvalidArgumentException
3940
*/
40-
public function __construct(string $enumeration)
41+
public function __construct(string $enumeration, iterable $map = null)
4142
{
4243
if (!\is_subclass_of($enumeration, Enum::class)) {
4344
throw new InvalidArgumentException(\sprintf(
@@ -47,10 +48,129 @@ public function __construct(string $enumeration)
4748
));
4849
}
4950
$this->enumeration = $enumeration;
51+
52+
if ($map) {
53+
$this->addIterable($map);
54+
}
55+
}
56+
57+
/* write access (mutable) */
58+
59+
/**
60+
* Adds the given enumerator (object or value) mapping to the specified data value.
61+
* @param Enum|null|bool|int|float|string|array $enumerator
62+
* @param mixed $value
63+
* @throws InvalidArgumentException On an invalid given enumerator
64+
* @see offsetSet()
65+
*/
66+
public function add($enumerator, $value): void
67+
{
68+
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
69+
$this->map[$ord] = $value;
5070
}
5171

5272
/**
53-
* Get the classname of the enumeration
73+
* Adds the given iterable, mapping enumerators (objects or values) to data values.
74+
* @param iterable $map
75+
* @throws InvalidArgumentException On an invalid given enumerator
76+
*/
77+
public function addIterable(iterable $map): void
78+
{
79+
$innerMap = $this->map;
80+
foreach ($map as $enumerator => $value) {
81+
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
82+
$innerMap[$ord] = $value;
83+
}
84+
$this->map = $innerMap;
85+
}
86+
87+
/**
88+
* Removes the given enumerator (object or value) mapping.
89+
* @param Enum|null|bool|int|float|string|array $enumerator
90+
* @throws InvalidArgumentException On an invalid given enumerator
91+
* @see offsetUnset()
92+
*/
93+
public function remove($enumerator): void
94+
{
95+
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
96+
unset($this->map[$ord]);
97+
}
98+
99+
/**
100+
* Removes the given iterable enumerator (object or value) mappings.
101+
* @param iterable $enumerators
102+
* @throws InvalidArgumentException On an invalid given enumerator
103+
*/
104+
public function removeIterable(iterable $enumerators): void
105+
{
106+
$map = $this->map;
107+
foreach ($enumerators as $enumerator) {
108+
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
109+
unset($map[$ord]);
110+
}
111+
112+
$this->map = $map;
113+
}
114+
115+
/* write access (immutable) */
116+
117+
/**
118+
* Creates a new map with the given enumerator (object or value) mapping to the specified data value added.
119+
* @param Enum|null|bool|int|float|string|array $enumerator
120+
* @param mixed $value
121+
* @return static
122+
* @throws InvalidArgumentException On an invalid given enumerator
123+
*/
124+
public function with($enumerator, $value): self
125+
{
126+
$clone = clone $this;
127+
$clone->add($enumerator, $value);
128+
return $clone;
129+
}
130+
131+
/**
132+
* Creates a new map with the given iterable mapping enumerators (objects or values) to data values added.
133+
* @param iterable $map
134+
* @return static
135+
* @throws InvalidArgumentException On an invalid given enumerator
136+
*/
137+
public function withIterable(iterable $map): self
138+
{
139+
$clone = clone $this;
140+
$clone->addIterable($map);
141+
return $clone;
142+
}
143+
144+
/**
145+
* Create a new map with the given enumerator mapping removed.
146+
* @param Enum|null|bool|int|float|string|array $enumerator
147+
* @return static
148+
* @throws InvalidArgumentException On an invalid given enumerator
149+
*/
150+
public function without($enumerator): self
151+
{
152+
$clone = clone $this;
153+
$clone->remove($enumerator);
154+
return $clone;
155+
}
156+
157+
/**
158+
* Creates a new map with the given iterable enumerator (object or value) mappings removed.
159+
* @param iterable $enumerators
160+
* @return static
161+
* @throws InvalidArgumentException On an invalid given enumerator
162+
*/
163+
public function withoutIterable(iterable $enumerators): self
164+
{
165+
$clone = clone $this;
166+
$clone->removeIterable($enumerators);
167+
return $clone;
168+
}
169+
170+
/* read access */
171+
172+
/**
173+
* Get the classname of the enumeration type.
54174
* @return string
55175
*/
56176
public function getEnumeration(): string
@@ -59,7 +179,29 @@ public function getEnumeration(): string
59179
}
60180

61181
/**
62-
* Get a list of map keys
182+
* Get the mapped data value of the given enumerator (object or value).
183+
* @param Enum|null|bool|int|float|string|array $enumerator
184+
* @return mixed
185+
* @throws InvalidArgumentException On an invalid given enumerator
186+
* @throws UnexpectedValueException If the given enumerator does not exist in this map
187+
* @see offsetGet()
188+
*/
189+
public function get($enumerator)
190+
{
191+
$enumerator = ($this->enumeration)::get($enumerator);
192+
$ord = $enumerator->getOrdinal();
193+
if (!\array_key_exists($ord, $this->map)) {
194+
throw new UnexpectedValueException(sprintf(
195+
'Enumerator %s could not be found',
196+
\var_export($enumerator->getValue(), true)
197+
));
198+
}
199+
200+
return $this->map[$ord];
201+
}
202+
203+
/**
204+
* Get a list of enumerator keys.
63205
* @return Enum[]
64206
*/
65207
public function getKeys(): array
@@ -68,7 +210,7 @@ public function getKeys(): array
68210
}
69211

70212
/**
71-
* Get a list of map values
213+
* Get a list of mapped data values.
72214
* @return mixed[]
73215
*/
74216
public function getValues(): array
@@ -77,10 +219,10 @@ public function getValues(): array
77219
}
78220

79221
/**
80-
* Search for the given value
222+
* Search for the given data value.
81223
* @param mixed $value
82224
* @param bool $strict Use strict type comparison
83-
* @return Enum|null The found key or NULL
225+
* @return Enum|null The enumerator object of the first matching data value or NULL
84226
*/
85227
public function search($value, bool $strict = false)
86228
{
@@ -93,7 +235,7 @@ public function search($value, bool $strict = false)
93235
}
94236

95237
/**
96-
* Test if the given enumerator exists
238+
* Test if the given enumerator key (object or value) exists.
97239
* @param Enum|null|bool|int|float|string|array $enumerator
98240
* @return bool
99241
* @see offsetExists
@@ -109,8 +251,10 @@ public function contains($enumerator): bool
109251
}
110252
}
111253

254+
/* ArrayAccess */
255+
112256
/**
113-
* Test if the given enumerator key exists and is not NULL
257+
* Test if the given enumerator key (object or value) exists and is not NULL
114258
* @param Enum|null|bool|int|float|string|array $enumerator
115259
* @return bool
116260
* @see contains
@@ -126,57 +270,52 @@ public function offsetExists($enumerator): bool
126270
}
127271

128272
/**
129-
* Get mapped data for the given enumerator
273+
* Get the mapped data value of the given enumerator (object or value).
130274
* @param Enum|null|bool|int|float|string|array $enumerator
131-
* @return mixed
275+
* @return mixed The mapped date value of the given enumerator or NULL
132276
* @throws InvalidArgumentException On an invalid given enumerator
133-
* @throws UnexpectedValueException If the given enumerator does not exist in this map
277+
* @see get()
134278
*/
135279
public function offsetGet($enumerator)
136280
{
137-
$enumerator = ($this->enumeration)::get($enumerator);
138-
$ord = $enumerator->getOrdinal();
139-
if (!isset($this->map[$ord]) && !\array_key_exists($ord, $this->map)) {
140-
throw new UnexpectedValueException(sprintf(
141-
'Enumerator %s could not be found',
142-
\var_export($enumerator->getValue(), true)
143-
));
281+
try {
282+
return $this->get($enumerator);
283+
} catch (UnexpectedValueException $e) {
284+
return null;
144285
}
145-
146-
return $this->map[$ord];
147286
}
148287

149288
/**
150-
* Attach a new enumerator or overwrite an existing one
289+
* Adds the given enumerator (object or value) mapping to the specified data value.
151290
* @param Enum|null|bool|int|float|string|array $enumerator
152291
* @param mixed $value
153292
* @return void
154293
* @throws InvalidArgumentException On an invalid given enumerator
155-
* @see attach()
294+
* @see add()
156295
*/
157296
public function offsetSet($enumerator, $value = null): void
158297
{
159-
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
160-
$this->map[$ord] = $value;
298+
$this->add($enumerator, $value);
161299
}
162300

163301
/**
164-
* Detach an existing enumerator
302+
* Removes the given enumerator (object or value) mapping.
165303
* @param Enum|null|bool|int|float|string|array $enumerator
166304
* @return void
167305
* @throws InvalidArgumentException On an invalid given enumerator
168-
* @see detach()
306+
* @see remove()
169307
*/
170308
public function offsetUnset($enumerator): void
171309
{
172-
$ord = ($this->enumeration)::get($enumerator)->getOrdinal();
173-
unset($this->map[$ord]);
310+
$this->remove($enumerator);
174311
}
175312

313+
/* IteratorAggregate */
314+
176315
/**
177316
* Get a new Iterator.
178317
*
179-
* @return Iterator
318+
* @return Iterator Iterator<K extends Enum, V>
180319
*/
181320
public function getIterator(): Iterator
182321
{
@@ -186,6 +325,8 @@ public function getIterator(): Iterator
186325
}
187326
}
188327

328+
/* Countable */
329+
189330
/**
190331
* Count the number of elements
191332
*

src/EnumSet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use IteratorAggregate;
1111

1212
/**
13-
* A set of enumerators of the given enumeration (EnumSet<T>)
13+
* A set of enumerators of the given enumeration (EnumSet<T extends Enum>)
1414
* based on an integer or binary bitset depending of given enumeration size.
1515
*
1616
* @copyright 2019 Marc Bennewitz

0 commit comments

Comments
 (0)