PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / dasprid / enum / src / EnumMap.php
ameliabooking / vendor / dasprid / enum / src Last commit date
Exception 8 months ago AbstractEnum.php 8 months ago EnumMap.php 8 months ago NullValue.php 8 months ago
EnumMap.php
386 lines
1 <?php
2 declare(strict_types = 1);
3
4 namespace DASPRiD\Enum;
5
6 use DASPRiD\Enum\Exception\ExpectationException;
7 use DASPRiD\Enum\Exception\IllegalArgumentException;
8 use IteratorAggregate;
9 use Serializable;
10 use Traversable;
11
12 /**
13 * A specialized map implementation for use with enum type keys.
14 *
15 * All of the keys in an enum map must come from a single enum type that is specified, when the map is created. Enum
16 * maps are represented internally as arrays. This representation is extremely compact and efficient.
17 *
18 * Enum maps are maintained in the natural order of their keys (the order in which the enum constants are declared).
19 * This is reflected in the iterators returned by the collection views {@see self::getIterator()} and
20 * {@see self::values()}.
21 *
22 * Iterators returned by the collection views are not consistent: They may or may not show the effects of modifications
23 * to the map that occur while the iteration is in progress.
24 */
25 final class EnumMap implements Serializable, IteratorAggregate
26 {
27 /**
28 * The class name of the key.
29 *
30 * @var string
31 */
32 private $keyType;
33
34 /**
35 * The type of the value.
36 *
37 * @var string
38 */
39 private $valueType;
40
41 /**
42 * @var bool
43 */
44 private $allowNullValues;
45
46 /**
47 * All of the constants comprising the enum, cached for performance.
48 *
49 * @var array<int, AbstractEnum>
50 */
51 private $keyUniverse;
52
53 /**
54 * Array representation of this map. The ith element is the value to which universe[i] is currently mapped, or null
55 * if it isn't mapped to anything, or NullValue if it's mapped to null.
56 *
57 * @var array<int, mixed>
58 */
59 private $values;
60
61 /**
62 * @var int
63 */
64 private $size = 0;
65
66 /**
67 * Creates a new enum map.
68 *
69 * @param string $keyType the type of the keys, must extend AbstractEnum
70 * @param string $valueType the type of the values
71 * @param bool $allowNullValues whether to allow null values
72 * @throws IllegalArgumentException when key type does not extend AbstractEnum
73 */
74 public function __construct(string $keyType, string $valueType, bool $allowNullValues)
75 {
76 if (! is_subclass_of($keyType, AbstractEnum::class)) {
77 throw new IllegalArgumentException(sprintf(
78 'Class %s does not extend %s',
79 $keyType,
80 AbstractEnum::class
81 ));
82 }
83
84 $this->keyType = $keyType;
85 $this->valueType = $valueType;
86 $this->allowNullValues = $allowNullValues;
87 $this->keyUniverse = $keyType::values();
88 $this->values = array_fill(0, count($this->keyUniverse), null);
89 }
90
91 public function __serialize(): array
92 {
93 $values = [];
94
95 foreach ($this->values as $ordinal => $value) {
96 if (null === $value) {
97 continue;
98 }
99
100 $values[$ordinal] = $this->unmaskNull($value);
101 }
102
103 return [
104 'keyType' => $this->keyType,
105 'valueType' => $this->valueType,
106 'allowNullValues' => $this->allowNullValues,
107 'values' => $values,
108 ];
109 }
110
111 public function __unserialize(array $data): void
112 {
113 $this->unserialize(serialize($data));
114 }
115
116 /**
117 * Checks whether the map types match the supplied ones.
118 *
119 * You should call this method when an EnumMap is passed to you and you want to ensure that it's made up of the
120 * correct types.
121 *
122 * @throws ExpectationException when supplied key type mismatches local key type
123 * @throws ExpectationException when supplied value type mismatches local value type
124 * @throws ExpectationException when the supplied map allows null values, abut should not
125 */
126 public function expect(string $keyType, string $valueType, bool $allowNullValues) : void
127 {
128 if ($keyType !== $this->keyType) {
129 throw new ExpectationException(sprintf(
130 'Callee expected an EnumMap with key type %s, but got %s',
131 $keyType,
132 $this->keyType
133 ));
134 }
135
136 if ($valueType !== $this->valueType) {
137 throw new ExpectationException(sprintf(
138 'Callee expected an EnumMap with value type %s, but got %s',
139 $keyType,
140 $this->keyType
141 ));
142 }
143
144 if ($allowNullValues !== $this->allowNullValues) {
145 throw new ExpectationException(sprintf(
146 'Callee expected an EnumMap with nullable flag %s, but got %s',
147 ($allowNullValues ? 'true' : 'false'),
148 ($this->allowNullValues ? 'true' : 'false')
149 ));
150 }
151 }
152
153 /**
154 * Returns the number of key-value mappings in this map.
155 */
156 public function size() : int
157 {
158 return $this->size;
159 }
160
161 /**
162 * Returns true if this map maps one or more keys to the specified value.
163 */
164 public function containsValue($value) : bool
165 {
166 return in_array($this->maskNull($value), $this->values, true);
167 }
168
169 /**
170 * Returns true if this map contains a mapping for the specified key.
171 */
172 public function containsKey(AbstractEnum $key) : bool
173 {
174 $this->checkKeyType($key);
175 return null !== $this->values[$key->ordinal()];
176 }
177
178 /**
179 * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
180 *
181 * More formally, if this map contains a mapping from a key to a value, then this method returns the value;
182 * otherwise it returns null (there can be at most one such mapping).
183 *
184 * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also
185 * possible that hte map explicitly maps the key to null. The {@see self::containsKey()} operation may be used to
186 * distinguish these two cases.
187 *
188 * @return mixed
189 */
190 public function get(AbstractEnum $key)
191 {
192 $this->checkKeyType($key);
193 return $this->unmaskNull($this->values[$key->ordinal()]);
194 }
195
196 /**
197 * Associates the specified value with the specified key in this map.
198 *
199 * If the map previously contained a mapping for this key, the old value is replaced.
200 *
201 * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
202 * (a null return can also indicate that the map previously associated null with the specified key.)
203 * @throws IllegalArgumentException when the passed values does not match the internal value type
204 */
205 public function put(AbstractEnum $key, $value)
206 {
207 $this->checkKeyType($key);
208
209 if (! $this->isValidValue($value)) {
210 throw new IllegalArgumentException(sprintf('Value is not of type %s', $this->valueType));
211 }
212
213 $index = $key->ordinal();
214 $oldValue = $this->values[$index];
215 $this->values[$index] = $this->maskNull($value);
216
217 if (null === $oldValue) {
218 ++$this->size;
219 }
220
221 return $this->unmaskNull($oldValue);
222 }
223
224 /**
225 * Removes the mapping for this key frm this map if present.
226 *
227 * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
228 * (a null return can also indicate that the map previously associated null with the specified key.)
229 */
230 public function remove(AbstractEnum $key)
231 {
232 $this->checkKeyType($key);
233
234 $index = $key->ordinal();
235 $oldValue = $this->values[$index];
236 $this->values[$index] = null;
237
238 if (null !== $oldValue) {
239 --$this->size;
240 }
241
242 return $this->unmaskNull($oldValue);
243 }
244
245 /**
246 * Removes all mappings from this map.
247 */
248 public function clear() : void
249 {
250 $this->values = array_fill(0, count($this->keyUniverse), null);
251 $this->size = 0;
252 }
253
254 /**
255 * Compares the specified map with this map for quality.
256 *
257 * Returns true if the two maps represent the same mappings.
258 */
259 public function equals(self $other) : bool
260 {
261 if ($this === $other) {
262 return true;
263 }
264
265 if ($this->size !== $other->size) {
266 return false;
267 }
268
269 return $this->values === $other->values;
270 }
271
272 /**
273 * Returns the values contained in this map.
274 *
275 * The array will contain the values in the order their corresponding keys appear in the map, which is their natural
276 * order (the order in which the num constants are declared).
277 */
278 public function values() : array
279 {
280 return array_values(array_map(function ($value) {
281 return $this->unmaskNull($value);
282 }, array_filter($this->values, function ($value) : bool {
283 return null !== $value;
284 })));
285 }
286
287 public function serialize() : string
288 {
289 return serialize($this->__serialize());
290 }
291
292 public function unserialize($serialized) : void
293 {
294 $data = unserialize($serialized);
295 $this->__construct($data['keyType'], $data['valueType'], $data['allowNullValues']);
296
297 foreach ($this->keyUniverse as $key) {
298 if (array_key_exists($key->ordinal(), $data['values'])) {
299 $this->put($key, $data['values'][$key->ordinal()]);
300 }
301 }
302 }
303
304 public function getIterator() : Traversable
305 {
306 foreach ($this->keyUniverse as $key) {
307 if (null === $this->values[$key->ordinal()]) {
308 continue;
309 }
310
311 yield $key => $this->unmaskNull($this->values[$key->ordinal()]);
312 }
313 }
314
315 private function maskNull($value)
316 {
317 if (null === $value) {
318 return NullValue::instance();
319 }
320
321 return $value;
322 }
323
324 private function unmaskNull($value)
325 {
326 if ($value instanceof NullValue) {
327 return null;
328 }
329
330 return $value;
331 }
332
333 /**
334 * @throws IllegalArgumentException when the passed key does not match the internal key type
335 */
336 private function checkKeyType(AbstractEnum $key) : void
337 {
338 if (get_class($key) !== $this->keyType) {
339 throw new IllegalArgumentException(sprintf(
340 'Object of type %s is not the same type as %s',
341 get_class($key),
342 $this->keyType
343 ));
344 }
345 }
346
347 private function isValidValue($value) : bool
348 {
349 if (null === $value) {
350 if ($this->allowNullValues) {
351 return true;
352 }
353
354 return false;
355 }
356
357 switch ($this->valueType) {
358 case 'mixed':
359 return true;
360
361 case 'bool':
362 case 'boolean':
363 return is_bool($value);
364
365 case 'int':
366 case 'integer':
367 return is_int($value);
368
369 case 'float':
370 case 'double':
371 return is_float($value);
372
373 case 'string':
374 return is_string($value);
375
376 case 'object':
377 return is_object($value);
378
379 case 'array':
380 return is_array($value);
381 }
382
383 return $value instanceof $this->valueType;
384 }
385 }
386