Passed
Push — generators ( b43b5e )
by Marc
04:21
created

Enum::has()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 3
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
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
13
 * @copyright Copyright (c) 2017 Marc Bennewitz
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
3 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
16
abstract class Enum
17
{
18
    /**
19
     * The selected enumerator value
20
     *
21
     * @var null|bool|int|float|string
22
     */
23
    private $value;
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "value" must contain a leading underscore
Loading history...
24
25
    /**
26
     * The ordinal number of the enumerator
27
     *
28
     * @var null|int
29
     */
30
    private $ordinal;
1 ignored issue
show
Coding Style introduced by
Private member variable "ordinal" must contain a leading underscore
Loading history...
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 = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "constants" must contain a leading underscore
Loading history...
38
39
    /**
40
     * A List of available enumerator names by enumeration class
41
     *
42
     * @var array ["$class" => ["$name0", ...], ...]
43
     */
44
    private static $names = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "names" must contain a leading underscore
Loading history...
45
46
    /**
47
     * Already instantiated enumerators
48
     *
49
     * @var array ["$class" => ["$name" => $instance, ...], ...]
50
     */
51
    private static $instances = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "instances" must contain a leading underscore
Loading history...
52
53
    /**
54
     * Constructor
55
     *
56
     * @param null|bool|int|float|string $value   The value of the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "null|boolean|integer|float|string" but found "null|bool|int|float|string" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
57
     * @param int|null                   $ordinal The ordinal number of the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "integer|null" but found "int|null" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
58
     */
59 36
    final private function __construct($value, $ordinal = null)
2 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$ordinal" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$ordinal"; expected 0 but found 1
Loading history...
60
    {
61 36
        $this->value   = $value;
62 36
        $this->ordinal = $ordinal;
63 36
    }
64
65
    /**
66
     * Get the name of the enumerator
67
     *
68
     * @return string
69
     * @see getName()
70
     */
71 1
    public function __toString()
72
    {
73 1
        return $this->getName();
74
    }
75
76
    /**
77
     * @throws LogicException Enums are not cloneable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
78
     *                        because instances are implemented as singletons
79
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
80 1
    final private function __clone()
81
    {
82 1
        throw new LogicException('Enums are not cloneable');
83
    }
84
85
    /**
86
     * @throws LogicException Enums are not serializable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
87
     *                        because instances are implemented as singletons
88
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
89 1
    final public function __sleep()
90
    {
91 1
        throw new LogicException('Enums are not serializable');
92
    }
93
94
    /**
95
     * @throws LogicException Enums are not serializable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
96
     *                        because instances are implemented as singletons
97
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
98 1
    final public function __wakeup()
99
    {
100 1
        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
1 ignored issue
show
Coding Style introduced by
Expected "null|boolean|integer|float|string" but found "null|bool|int|float|string" for function return type
Loading history...
107
     */
108 23
    final public function getValue()
109
    {
110 23
        return $this->value;
111
    }
112
113
    /**
114
     * Get the name of the enumerator
115
     *
116
     * @return string
117
     */
118 7
    final public function getName()
119
    {
120 7
        $ordinal = $this->ordinal !== null ? $this->ordinal : $this->getOrdinal();
121 7
        return self::$names[static::class][$ordinal];
122
    }
123
124
    /**
125
     * Get the ordinal number of the enumerator
126
     *
127
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
128
     */
129 63
    final public function getOrdinal()
130
    {
131 63
        if ($this->ordinal === null) {
132 19
            $ordinal = 0;
133 19
            $value   = $this->value;
134 19
            foreach (self::detectConstants(static::class) as $constValue) {
135 19
                if ($value === $constValue) {
136 19
                    break;
137
                }
138 16
                ++$ordinal;
139
            }
140
141 19
            $this->ordinal = $ordinal;
142
        }
143
144 63
        return $this->ordinal;
145
    }
146
147
    /**
148
     * Compare this enumerator against another and check if it's the same.
149
     *
150
     * @param mixed $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
151
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
152
     */
153 2
    final public function is($enumerator)
154
    {
155 2
        return $this === $enumerator || $this->value === $enumerator
156
157
            // The following additional conditions are required only because of the issue of serializable singletons
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 116 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
158 2
            || ($enumerator instanceof static
159 2
                && \get_class($enumerator) === static::class
160 2
                && $enumerator->value === $this->value
161
            );
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
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "static|null|boolean|integer|float|string" but found "static|null|bool|int|float|string" for parameter type
Loading history...
168
     * @return static
169
     * @throws InvalidArgumentException On an unknwon or invalid value
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
170
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
171
     */
172 73
    final public static function get($enumerator)
173
    {
174 73
        if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
175 28
            return $enumerator;
176
        }
177
178 52
        return static::byValue($enumerator);
179
    }
180
181
    /**
182
     * Get an enumerator instance by the given value
183
     *
184
     * @param mixed $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
185
     * @return static
186
     * @throws InvalidArgumentException On an unknwon or invalid value
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
187
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
188
     */
189 52
    final public static function byValue($value)
190
    {
191 52
        $constants = self::detectConstants(static::class);
192 50
        $name      = \array_search($value, $constants, true);
193 50
        if ($name === false) {
194 7
            $message = \is_scalar($value)
195 3
                ? 'Unknown value ' . \var_export($value, true)
1 ignored issue
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
196 7
                : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value));
2 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 106 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
197 7
            throw new InvalidArgumentException($message);
198
        }
199
200 44
        if (!isset(self::$instances[static::class][$name])) {
201 17
            self::$instances[static::class][$name] = new static($constants[$name]);
202
        }
203
204 44
        return self::$instances[static::class][$name];
205
    }
206
207
    /**
208
     * Get an enumerator instance by the given name
209
     *
210
     * @param string $name The name of the enumerator
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
211
     * @return static
212
     * @throws InvalidArgumentException On an invalid or unknown name
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
213
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
214
     */
215 44
    final public static function byName($name)
216
    {
217 44
        $name = (string) $name;
218 44
        if (isset(self::$instances[static::class][$name])) {
219 32
            return self::$instances[static::class][$name];
220
        }
221
222 17
        $const = static::class . '::' . $name;
223 17
        if (!\defined($const)) {
224 1
            throw new InvalidArgumentException($const . ' not defined');
225
        }
226
227 16
        return self::$instances[static::class][$name] = new static(\constant($const));
228
    }
229
230
    /**
231
     * Get an enumeration instance by the given ordinal number
232
     *
233
     * @param int $ordinal The ordinal number or the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
234
     * @return static
235
     * @throws InvalidArgumentException On an invalid ordinal number
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
236
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
237
     */
238 30
    final public static function byOrdinal($ordinal)
239
    {
240 30
        $ordinal = (int) $ordinal;
241
242 30
        if (!isset(self::$names[static::class])) {
243 2
            self::detectConstants(static::class);
244
        }
245
246 30
        if (!isset(self::$names[static::class][$ordinal])) {
247 1
            throw new InvalidArgumentException(\sprintf(
248 1
                'Invalid ordinal number, must between 0 and %s',
249 1
                \count(self::$names[static::class]) - 1
250
            ));
251
        }
252
253 29
        $name = self::$names[static::class][$ordinal];
254 29
        if (isset(self::$instances[static::class][$name])) {
255 27
            return self::$instances[static::class][$name];
256
        }
257
258 3
        $const = static::class . '::' . $name;
259 3
        return self::$instances[static::class][$name] = new static(\constant($const), $ordinal);
260
    }
261
262
    /**
263
     * Get a list of enumerator instances ordered by ordinal number
264
     *
265
     * @return static[]
266
     */
267 16
    final public static function getEnumerators()
268
    {
269 16
        if (!isset(self::$names[static::class])) {
270 1
            self::detectConstants(static::class);
271
        }
272 16
        return \array_map([static::class, 'byName'], self::$names[static::class]);
273
    }
274
275
    /**
276
     * Get a list of enumerator values ordered by ordinal number
277
     *
278
     * @return mixed[]
279
     */
280 1
    final public static function getValues()
281
    {
282 1
        return \array_values(self::detectConstants(static::class));
283
    }
284
285
    /**
286
     * Get a list of enumerator names ordered by ordinal number
287
     *
288
     * @return string[]
289
     */
290 2
    final public static function getNames()
291
    {
292 2
        if (!isset(self::$names[static::class])) {
293 1
            self::detectConstants(static::class);
294
        }
295 2
        return self::$names[static::class];
296
    }
297
    /*
298
     * Get a list of enumerator ordinal numbers
299
     *
300
     * @return int[]
301
     */
302 1
    final public static function getOrdinals()
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
303
    {
304 1
        $count = \count(self::detectConstants(static::class));
305 1
        return $count === 0 ? [] : \range(0, $count - 1);
306
    }
307
308
    /**
309
     * Get all available constants of the called class
310
     *
311
     * @return array
312
     * @throws LogicException On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
313
     */
314 78
    final public static function getConstants()
315
    {
316 78
        return self::detectConstants(static::class);
317
    }
318
319
    /**
320
     * Is the given enumerator part of this enumeration
321
     * 
322
     * @param static|null|bool|int|float|string $value
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "static|null|boolean|integer|float|string" but found "static|null|bool|int|float|string" for parameter type
Loading history...
323
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
324
     */
325 1
    final public static function has($value)
326
    {
327 1
        if ($value instanceof static && \get_class($value) === static::class) {
328 1
            return true;
329
        }
330
331 1
        $constants = self::detectConstants(static::class);
332 1
        return \in_array($value, $constants, true);
333
    }
334
335
    /**
336
     * Detect all public available constants of given enumeration class
337
     *
338
     * @param string $class
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
339
     * @return array
340
     * @throws LogicException On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
341
     */
342 102
    private static function detectConstants($class)
343
    {
344 102
        if (!isset(self::$constants[$class])) {
345 35
            $reflection = new ReflectionClass($class);
346 35
            $constants  = [];
347
348
            do {
349 35
                $scopeConstants = [];
350 35
                if (PHP_VERSION_ID >= 70100) {
351
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
352
                    // for enumerations we are only interested in public once.
353
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
354
                        if ($reflConstant->isPublic()) {
355
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
356
                        }
357
                    }
358
                } else {
359
                    // In PHP < 7.1 all class constants were public by definition
360 35
                    $scopeConstants = $reflection->getConstants();
361
                }
362
363 35
                $constants = $scopeConstants + $constants;
364 35
            } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
365
366
            // Detect ambiguous values and report names
367 35
            $ambiguous = [];
368 35
            foreach ($constants as $value) {
369 34
                $names = \array_keys($constants, $value, true);
370 34
                if (\count($names) > 1) {
371 34
                    $ambiguous[\var_export($value, true)] = $names;
372
                }
373
            }
374 35
            if (!empty($ambiguous)) {
375 2
                throw new LogicException(
376
                    'All possible values needs to be unique. The following are ambiguous: '
377 2
                    . \implode(', ', \array_map(function ($names) use ($constants) {
378 2
                        return \implode('/', $names) . '=' . \var_export($constants[$names[0]], true);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 102 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
379 2
                    }, $ambiguous))
380
                );
381
            }
382
383 33
            self::$constants[$class] = $constants;
384 33
            self::$names[$class] = \array_keys($constants);
385
        }
386
387 100
        return self::$constants[$class];
388
    }
389
390
    /**
391
     * Get an enumerator instance by the given name.
392
     *
393
     * This will be called automatically on calling a method
394
     * with the same name of a defined enumerator.
395
     *
396
     * @param string $method The name of the enumeraotr (called as method)
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
397
     * @param array  $args   There should be no arguments
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
398
     * @return static
399
     * @throws InvalidArgumentException On an invalid or unknown name
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
400
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
401
     */
402 26
    final public static function __callStatic($method, array $args)
403
    {
404 26
        return self::byName($method);
405
    }
406
}
407