Passed
Push — master ( 93b7bb...5c048f )
by Marc
01:43 queued 11s
created

EnumMap   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 77
dl 0
loc 356
ccs 103
cts 103
cp 1
rs 9.68
c 0
b 0
f 0
wmc 34

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A has() 0 8 2
A add() 0 4 1
A withoutIterable() 0 5 1
A with() 0 5 1
A addIterable() 0 8 2
A getKeys() 0 3 1
A removeIterable() 0 9 2
A getValues() 0 3 1
A isEmpty() 0 3 1
A withIterable() 0 5 1
A get() 0 12 2
A remove() 0 4 1
A without() 0 5 1
A contains() 0 3 1
A getEnumeration() 0 3 1
A offsetExists() 0 7 2
A getIterator() 0 5 2
A offsetGet() 0 6 2
A offsetUnset() 0 3 1
A count() 0 3 1
A offsetSet() 0 3 1
A search() 0 8 2
A __debugInfo() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MabeEnum;
6
7
use ArrayAccess;
8
use Countable;
9
use InvalidArgumentException;
10
use Iterator;
11
use IteratorAggregate;
12
use UnexpectedValueException;
13
14
/**
15
 * A map of enumerators and data values (EnumMap<K extends Enum, V>).
16
 *
17
 * @copyright 2019 Marc Bennewitz
18
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
19
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
20
 */
21
class EnumMap implements ArrayAccess, Countable, IteratorAggregate
22
{
23
    /**
24
     * The classname of the enumeration type
25
     * @var string
26
     */
27
    private $enumeration;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
28
29
    /**
30
     * Internal map of ordinal number and data value
31
     * @var array
32
     */
33
    private $map = [];
34
35
    /**
36
     * Constructor
37
     * @param string $enumeration The classname of the enumeration type
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
38
     * @param null|iterable $map Initialize map
0 ignored issues
show
Coding Style introduced by
Expected 9 spaces after parameter name; 1 found
Loading history...
39
     * @throws InvalidArgumentException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
40
     */
41 21
    public function __construct(string $enumeration, iterable $map = null)
42
    {
43 21
        if (!\is_subclass_of($enumeration, Enum::class)) {
44 1
            throw new InvalidArgumentException(\sprintf(
45 1
                '%s can handle subclasses of %s only',
46 1
                 __CLASS__,
47 1
                Enum::class
48
            ));
49
        }
50 20
        $this->enumeration = $enumeration;
51
52 20
        if ($map) {
0 ignored issues
show
introduced by
$map is of type iterable|null, thus it always evaluated to false.
Loading history...
53 3
            $this->addIterable($map);
54
        }
55 20
    }
56
57
    /**
58
     * Add virtual private property "__pairs" with a list of key-value-pairs
59
     * to the result of var_dump.
60
     *
61
     * This helps debugging as internally the map is using the ordinal number.
62
     *
63
     * @return array<string, mixed>
64
     */
65 1
    public function __debugInfo(): array {
66 1
        $dbg = (array)$this;
67 1
        $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) {
68 1
            return [$k, $v];
69 1
        }, $this->getKeys(), $this->getValues());
70 1
        return $dbg;
71
    }
72
73
    /* write access (mutable) */
74
75
    /**
76
     * Adds the given enumerator (object or value) mapping to the specified data value.
77
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
78
     * @param mixed                                 $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
79
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
80
     * @see offsetSet()
81
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
82 13
    public function add($enumerator, $value): void
83
    {
84 13
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
85 12
        $this->map[$ord] = $value;
86 12
    }
87
88
    /**
89
     * Adds the given iterable, mapping enumerators (objects or values) to data values.
90
     * @param iterable $map
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
91
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
92
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
93 5
    public function addIterable(iterable $map): void
94
    {
95 5
        $innerMap = $this->map;
96 5
        foreach ($map as $enumerator => $value) {
97 5
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
98 5
            $innerMap[$ord] = $value;
99
        }
100 5
        $this->map = $innerMap;
101 5
    }
102
103
    /**
104
     * Removes the given enumerator (object or value) mapping.
105
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
106
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
107
     * @see offsetUnset()
108
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
109 5
    public function remove($enumerator): void
110
    {
111 5
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
112 5
        unset($this->map[$ord]);
113 5
    }
114
115
    /**
116
     * Removes the given iterable enumerator (object or value) mappings.
117
     * @param iterable $enumerators
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
118
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
119
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
120 3
    public function removeIterable(iterable $enumerators): void
121
    {
122 3
        $map = $this->map;
123 3
        foreach ($enumerators as $enumerator) {
124 3
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
125 3
            unset($map[$ord]);
126
        }
127
128 3
        $this->map = $map;
129 3
    }
130
131
    /* write access (immutable) */
132
133
    /**
134
     * Creates a new map with the given enumerator (object or value) mapping to the specified data value added.
135
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
136
     * @param mixed                                 $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
137
     * @return static
138
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
139
     */
140 1
    public function with($enumerator, $value): self
141
    {
142 1
        $clone = clone $this;
143 1
        $clone->add($enumerator, $value);
144 1
        return $clone;
145
    }
146
147
    /**
148
     * Creates a new map with the given iterable mapping enumerators (objects or values) to data values added.
149
     * @param iterable $map
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
150
     * @return static
151
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
152
     */
153 1
    public function withIterable(iterable $map): self
154
    {
155 1
        $clone = clone $this;
156 1
        $clone->addIterable($map);
157 1
        return $clone;
158
    }
159
160
    /**
161
     * Create a new map with the given enumerator mapping removed.
162
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
163
     * @return static
164
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
165
     */
166 1
    public function without($enumerator): self
167
    {
168 1
        $clone = clone $this;
169 1
        $clone->remove($enumerator);
170 1
        return $clone;
171
    }
172
173
    /**
174
     * Creates a new map with the given iterable enumerator (object or value) mappings removed.
175
     * @param iterable $enumerators
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
176
     * @return static
177
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
178
     */
179 1
    public function withoutIterable(iterable $enumerators): self
180
    {
181 1
        $clone = clone $this;
182 1
        $clone->removeIterable($enumerators);
183 1
        return $clone;
184
    }
185
186
    /* read access */
187
188
    /**
189
     * Get the classname of the enumeration type.
190
     * @return string
191
     */
192 1
    public function getEnumeration(): string
193
    {
194 1
        return $this->enumeration;
195
    }
196
197
    /**
198
     * Get the mapped data value of the given enumerator (object or value).
199
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
200
     * @return mixed
201
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
202
     * @throws UnexpectedValueException If the given enumerator does not exist in this map
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
203
     * @see offsetGet()
204
     */
205 10
    public function get($enumerator)
206
    {
207 10
        $enumerator = ($this->enumeration)::get($enumerator);
208 10
        $ord = $enumerator->getOrdinal();
209 10
        if (!\array_key_exists($ord, $this->map)) {
210 2
            throw new UnexpectedValueException(sprintf(
211 2
                'Enumerator %s could not be found',
212 2
                \var_export($enumerator->getValue(), true)
213
            ));
214
        }
215
216 8
        return $this->map[$ord];
217
    }
218
219
    /**
220
     * Get a list of enumerator keys.
221
     * @return Enum[]
222
     */
223 8
    public function getKeys(): array
224
    {
225 8
        return \array_map([$this->enumeration, 'byOrdinal'], \array_keys($this->map));
226
    }
227
228
    /**
229
     * Get a list of mapped data values.
230
     * @return mixed[]
231
     */
232 8
    public function getValues(): array
233
    {
234 8
        return \array_values($this->map);
235
    }
236
237
    /**
238
     * Search for the given data value.
239
     * @param mixed $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
240
     * @param bool $strict Use strict type comparison
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
241
     * @return Enum|null The enumerator object of the first matching data value or NULL
242
     */
243 2
    public function search($value, bool $strict = false)
244
    {
245 2
        $ord = \array_search($value, $this->map, $strict);
246 2
        if ($ord !== false) {
247 2
            return ($this->enumeration)::byOrdinal($ord);
248
        }
249
250 2
        return null;
251
    }
252
253
    /**
254
     * Test if the given enumerator key (object or value) exists.
255
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
256
     * @return bool
257
     * @see offsetExists()
258
     */
259 8
    public function has($enumerator): bool
260
    {
261
        try {
262 8
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
263 7
            return \array_key_exists($ord, $this->map);
264 1
        } catch (InvalidArgumentException $e) {
265
            // An invalid enumerator can't be contained in this map
266 1
            return false;
267
        }
268
    }
269
270
    /**
271
     * Test if the given enumerator key (object or value) exists.
272
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
273
     * @return bool
274
     * @see offsetExists()
275
     * @see has()
276
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
277
     */
278 1
    public function contains($enumerator): bool
279
    {
280 1
        return $this->has($enumerator);
281
    }
282
283
    /* ArrayAccess */
284
285
    /**
286
     * Test if the given enumerator key (object or value) exists and is not NULL
287
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
288
     * @return bool
289
     * @see contains()
290
     */
291 5
    public function offsetExists($enumerator): bool
292
    {
293
        try {
294 5
            return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]);
295 1
        } catch (InvalidArgumentException $e) {
296
            // An invalid enumerator can't be an offset of this map
297 1
            return false;
298
        }
299
    }
300
301
    /**
302
     * Get the mapped data value of the given enumerator (object or value).
303
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
304
     * @return mixed The mapped date value of the given enumerator or NULL
305
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
306
     * @see get()
307
     */
308 4
    public function offsetGet($enumerator)
309
    {
310
        try {
311 4
            return $this->get($enumerator);
312 1
        } catch (UnexpectedValueException $e) {
313 1
            return null;
314
        }
315
    }
316
317
    /**
318
     * Adds the given enumerator (object or value) mapping to the specified data value.
319
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
320
     * @param mixed                                 $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
321
     * @return void
322
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
323
     * @see add()
324
     */
325 7
    public function offsetSet($enumerator, $value = null): void
326
    {
327 7
        $this->add($enumerator, $value);
328 6
    }
329
330
    /**
331
     * Removes the given enumerator (object or value) mapping.
332
     * @param Enum|null|bool|int|float|string|array $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
333
     * @return void
334
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
335
     * @see remove()
336
     */
337 2
    public function offsetUnset($enumerator): void
338
    {
339 2
        $this->remove($enumerator);
340 2
    }
341
342
    /* IteratorAggregate */
343
344
    /**
345
     * Get a new Iterator.
346
     *
347
     * @return Iterator Iterator<K extends Enum, V>
348
     */
349 2
    public function getIterator(): Iterator
350
    {
351 2
        $map = $this->map;
352 2
        foreach ($map as $ordinal => $value) {
353 2
            yield ($this->enumeration)::byOrdinal($ordinal) => $value;
354
        }
355 2
    }
356
357
    /* Countable */
358
359
    /**
360
     * Count the number of elements
361
     *
362
     * @return int
363
     */
364 3
    public function count(): int
365
    {
366 3
        return \count($this->map);
367
    }
368
369
    /**
370
     * Tests if the map is empty
371
     *
372
     * @return bool
373
     */
374 1
    public function isEmpty(): bool
375
    {
376 1
        return empty($this->map);
377
    }
378
}
379