Completed
Push — feature/bench ( eaf7cd...0563be )
by Marc
64:09 queued 60:48
created

Enum::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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