Completed
Pull Request — master (#102)
by Krishnaprasad
02:48
created

Enum::hasName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace MabeEnum;
4
5
use ReflectionClass;
6
use InvalidArgumentException;
7
use LogicException;
8
9
/**
10
 * Class to implement enumerations for PHP 5 (without SplEnum)
11
 *
12
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
 * @copyright Copyright (c) 2017 Marc Bennewitz
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
16
abstract class Enum
17
{
18
    /**
19
     * The selected enumerator value
20
     *
21
     * @var null|bool|int|float|string
22
     */
23
    private $value;
24
25
    /**
26
     * The ordinal number of the enumerator
27
     *
28
     * @var null|int
29
     */
30
    private $ordinal;
31
32
    /**
33
     * A map of enumerator names and values by enumeration class
34
     *
35
     * @var array ["$class" => ["$name" => $value, ...], ...]
36
     */
37
    private static $constants = [];
38
39
    /**
40
     * A List of available enumerator names by enumeration class
41
     *
42
     * @var array ["$class" => ["$name0", ...], ...]
43
     */
44
    private static $names = [];
45
46
    /**
47
     * Already instantiated enumerators
48
     *
49
     * @var array ["$class" => ["$name" => $instance, ...], ...]
50
     */
51
    private static $instances = [];
52
53
    /**
54
     * Constructor
55
     *
56
     * @param null|bool|int|float|string $value   The value of the enumerator
57
     * @param int|null                   $ordinal The ordinal number of the enumerator
58
     */
59 152
    final private function __construct($value, $ordinal = null)
60
    {
61 152
        $this->value   = $value;
62 152
        $this->ordinal = $ordinal;
63 152
    }
64
65
    /**
66
     * Get the name of the enumerator
67
     *
68
     * @return string
69
     * @see getName()
70
     */
71 4
    public function __toString()
72
    {
73 4
        return $this->getName();
74
    }
75
76
    /**
77
     * @throws LogicException Enums are not cloneable
78
     *                        because instances are implemented as singletons
79
     */
80 4
    final private function __clone()
81
    {
82 4
        throw new LogicException('Enums are not cloneable');
83
    }
84
85
    /**
86
     * @throws LogicException Enums are not serializable
87
     *                        because instances are implemented as singletons
88
     */
89 4
    final public function __sleep()
90
    {
91 4
        throw new LogicException('Enums are not serializable');
92
    }
93
94
    /**
95
     * @throws LogicException Enums are not serializable
96
     *                        because instances are implemented as singletons
97
     */
98 4
    final public function __wakeup()
99
    {
100 4
        throw new LogicException('Enums are not serializable');
101
    }
102
103
    /**
104
     * Get the value of the enumerator
105
     *
106
     * @return null|bool|int|float|string
107
     */
108 96
    final public function getValue()
109
    {
110 96
        return $this->value;
111
    }
112
113
    /**
114
     * Get the name of the enumerator
115
     *
116
     * @return string
117
     */
118 36
    final public function getName()
119
    {
120 36
        $ordinal = $this->ordinal !== null ? $this->ordinal : $this->getOrdinal();
121 36
        return self::$names[static::class][$ordinal];
122
    }
123
124
    /**
125
     * Get the ordinal number of the enumerator
126
     *
127
     * @return int
128
     */
129 256
    final public function getOrdinal()
130
    {
131 256
        if ($this->ordinal === null) {
132 80
            $ordinal = 0;
133 80
            $value   = $this->value;
134 80
            foreach (self::detectConstants(static::class) as $constValue) {
135 80
                if ($value === $constValue) {
136 80
                    break;
137
                }
138 64
                ++$ordinal;
139 20
            }
140
141 80
            $this->ordinal = $ordinal;
142 20
        }
143
144 256
        return $this->ordinal;
145
    }
146
147
    /**
148
     * Compare this enumerator against another and check if it's the same.
149
     *
150
     * @param mixed $enumerator
151
     * @return bool
152
     */
153 8
    final public function is($enumerator)
154
    {
155 8
        return $this === $enumerator || $this->value === $enumerator
156
157
            // The following additional conditions are required only because of the issue of serializable singletons
158 8
            || ($enumerator instanceof static
159 8
                && \get_class($enumerator) === static::class
160 8
                && $enumerator->value === $this->value
161 2
            );
162
    }
163
164
    /**
165
     * Get an enumerator instance of the given enumerator value or instance
166
     *
167
     * @param static|null|bool|int|float|string $enumerator
168
     * @return static
169
     * @throws InvalidArgumentException On an unknwon or invalid value
170
     * @throws LogicException           On ambiguous constant values
171
     */
172 304
    final public static function get($enumerator)
173
    {
174 304
        if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
175 112
            return $enumerator;
176
        }
177
178 220
        return static::byValue($enumerator);
179
    }
180
181
    /**
182
     * Get an enumerator instance by the given value
183
     *
184
     * @param mixed $value
185
     * @return static
186
     * @throws InvalidArgumentException On an unknwon or invalid value
187
     * @throws LogicException           On ambiguous constant values
188
     */
189 220
    final public static function byValue($value)
190
    {
191 220
        if (!isset(self::$constants[static::class])) {
192 64
            self::detectConstants(static::class);
193 14
        }
194
195 212
        $name = \array_search($value, self::$constants[static::class], true);
196 212
        if ($name === false) {
197 40
            throw new InvalidArgumentException(sprintf(
198 40
                'Unknown value %s for enumeration %s',
199 40
                \is_scalar($value)
200 25
                    ? \var_export($value, true)
201 40
                    : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)),
202 40
                static::class
203 10
            ));
204
        }
205
206 176
        if (!isset(self::$instances[static::class][$name])) {
207 68
            self::$instances[static::class][$name] = new static(self::$constants[static::class][$name]);
208 17
        }
209
210 176
        return self::$instances[static::class][$name];
211
    }
212
213
    /**
214
     * Get an enumerator instance by the given name
215
     *
216
     * @param string $name The name of the enumerator
217
     * @return static
218
     * @throws InvalidArgumentException On an invalid or unknown name
219
     * @throws LogicException           On ambiguous values
220
     */
221 184
    final public static function byName($name)
222
    {
223 184
        $name = (string) $name;
224 184
        if (isset(self::$instances[static::class][$name])) {
225 128
            return self::$instances[static::class][$name];
226
        }
227
228 76
        $const = static::class . '::' . $name;
229 76
        if (!\defined($const)) {
230 4
            throw new InvalidArgumentException($const . ' not defined');
231
        }
232
233 72
        return self::$instances[static::class][$name] = new static(\constant($const));
234
    }
235
236
    /**
237
     * Get an enumeration instance by the given ordinal number
238
     *
239
     * @param int $ordinal The ordinal number or the enumerator
240
     * @return static
241
     * @throws InvalidArgumentException On an invalid ordinal number
242
     * @throws LogicException           On ambiguous values
243
     */
244 120
    final public static function byOrdinal($ordinal)
245
    {
246 120
        $ordinal = (int) $ordinal;
247
248 120
        if (!isset(self::$names[static::class])) {
249 8
            self::detectConstants(static::class);
250 2
        }
251
252 120
        if (!isset(self::$names[static::class][$ordinal])) {
253 4
            throw new InvalidArgumentException(\sprintf(
254 4
                'Invalid ordinal number, must between 0 and %s',
255 4
                \count(self::$names[static::class]) - 1
256 1
            ));
257
        }
258
259 116
        $name = self::$names[static::class][$ordinal];
260 116
        if (isset(self::$instances[static::class][$name])) {
261 108
            return self::$instances[static::class][$name];
262
        }
263
264 12
        return self::$instances[static::class][$name] = new static(self::$constants[static::class][$name], $ordinal);
265
    }
266
267
    /**
268
     * Get a list of enumerator instances ordered by ordinal number
269
     *
270
     * @return static[]
271
     */
272 64
    final public static function getEnumerators()
273
    {
274 64
        if (!isset(self::$names[static::class])) {
275 4
            self::detectConstants(static::class);
276 1
        }
277 64
        return \array_map([static::class, 'byName'], self::$names[static::class]);
278
    }
279
280
    /**
281
     * Get a list of enumerator values ordered by ordinal number
282
     *
283
     * @return mixed[]
284
     */
285 4
    final public static function getValues()
286
    {
287 4
        return \array_values(self::detectConstants(static::class));
288
    }
289
290
    /**
291
     * Get a list of enumerator names ordered by ordinal number
292
     *
293
     * @return string[]
294
     */
295 8
    final public static function getNames()
296
    {
297 8
        if (!isset(self::$names[static::class])) {
298 4
            self::detectConstants(static::class);
299 1
        }
300 8
        return self::$names[static::class];
301
    }
302
    
303
    /**
304
     * Get a list of enumerator ordinal numbers
305
     *
306
     * @return int[]
307
     */
308 4
    final public static function getOrdinals()
309
    {
310 4
        $count = \count(self::detectConstants(static::class));
311 4
        return $count === 0 ? [] : \range(0, $count - 1);
312
    }
313
314
    /**
315
     * Get all available constants of the called class
316
     *
317
     * @return array
318
     * @throws LogicException On ambiguous constant values
319
     */
320 316
    final public static function getConstants()
321
    {
322 316
        return self::detectConstants(static::class);
323
    }
324
325
    /**
326
     * Is the given name part of this enumeration
327
     *
328
     * @param string $name
329
     * @return bool
330
     */
331 4
    final public static function hasName($name)
332
    {
333 4
        return \is_string($name) && \defined("static::$name");
334
    }
335
336
    /**
337
     * Is the given enumerator part of this enumeration
338
     * 
339
     * @param static|null|bool|int|float|string $value
340
     * @return bool
341
     */
342 4
    final public static function has($value)
343
    {
344 4
        if ($value instanceof static && \get_class($value) === static::class) {
345 4
            return true;
346
        }
347
348 4
        $constants = self::detectConstants(static::class);
349 4
        return \in_array($value, $constants, true);
350
    }
351
352
    /**
353
     * Detect all public available constants of given enumeration class
354
     *
355
     * @param string $class
356
     * @return array
357
     */
358 400
    private static function detectConstants($class)
359
    {
360 400
        if (!isset(self::$constants[$class])) {
361 160
            $reflection = new ReflectionClass($class);
362 160
            $publicConstants  = [];
363
364
            do {
365 160
                $scopeConstants = [];
366 160
                if (\PHP_VERSION_ID >= 70100 && method_exists(ReflectionClass::class, 'getReflectionConstants')) {
367
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
368
                    // for enumerations we are only interested in public once.
369
                    // NOTE: HHVM > 3.26.2 still does not support private/protected constants.
370
                    //       It allows the visibility keyword but ignores it.
371 82
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
372 80
                        if ($reflConstant->isPublic()) {
373 82
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
374
                        }
375
                    }
376
                } else {
377
                    // In PHP < 7.1 all class constants were public by definition
378 78
                    $scopeConstants = $reflection->getConstants();
379
                }
380
381 160
                $publicConstants = $scopeConstants + $publicConstants;
382 160
            } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);
383
384 160
            assert(
385 160
                self::noAmbiguousValues($publicConstants),
386 160
                "Ambiguous enumerator values detected for {$class}"
387 39
            );
388
389 152
            self::$constants[$class] = $publicConstants;
390 152
            self::$names[$class] = \array_keys($publicConstants);
391 37
        }
392
393 392
        return self::$constants[$class];
394
    }
395
396
    /**
397
     * Test that the given constants does not contain ambiguous values
398
     * @param array $constants
399
     * @return bool
400
     */
401 160
    private static function noAmbiguousValues(array $constants)
402
    {
403 160
        foreach ($constants as $value) {
404 156
            $names = \array_keys($constants, $value, true);
405 156
            if (\count($names) > 1) {
406 122
                return false;
407
            }
408 37
        }
409
410 144
        return true;
411
    }
412
413
    /**
414
     * Get an enumerator instance by the given name.
415
     *
416
     * This will be called automatically on calling a method
417
     * with the same name of a defined enumerator.
418
     *
419
     * @param string $method The name of the enumeraotr (called as method)
420
     * @param array  $args   There should be no arguments
421
     * @return static
422
     * @throws InvalidArgumentException On an invalid or unknown name
423
     * @throws LogicException           On ambiguous constant values
424
     */
425 108
    final public static function __callStatic($method, array $args)
426
    {
427 108
        return self::byName($method);
428
    }
429
}
430