Passed
Push — master ( aaef51...3a5af4 )
by Marc
03:19
created

Enum::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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 152
    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 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
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 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
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 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
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 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
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 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
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
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
            }
140
141 80
            $this->ordinal = $ordinal;
142
        }
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
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 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
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 8
            || ($enumerator instanceof static
159 8
                && \get_class($enumerator) === static::class
160 8
                && $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 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
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 220
    final public static function byValue($value)
190
    {
191 220
        if (!isset(self::$constants[static::class])) {
192 64
            self::detectConstants(static::class);
193
        }
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 20
                    ? \var_export($value, true)
1 ignored issue
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
201 40
                    : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)),
1 ignored issue
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
202 40
                static::class
203
            ));
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]);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 104 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...
208
        }
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
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
217
     * @return static
218
     * @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...
219
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
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
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...
240
     * @return static
241
     * @throws InvalidArgumentException On an invalid ordinal number
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
242
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
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
        }
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
            ));
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);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 117 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...
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
        }
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
        }
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
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
319
     */
320 318
    final public static function getConstants()
321
    {
322 318
        return self::detectConstants(static::class);
323
    }
324
325
    /**
0 ignored issues
show
Documentation introduced by
Doc comment for parameter "$enumerator" missing
Loading history...
326
     * Test if the given enumerator is part of this enumeration
327
     * 
328
     * @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...
Bug introduced by
Doc comment for parameter $value does not match actual variable name $enumerator
Loading history...
329
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
330
     */
331 4
    final public static function has($enumerator)
332
    {
333 4
        if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
334 4
            return true;
335
        }
336
337 4
        return static::hasValue($enumerator);
338
    }
339
340
    /**
341
     * Test if the given enumerator value is part of this enumeration
342
     *
343
     * @param null|bool|int|float|string $value
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "null|boolean|integer|float|string" but found "null|bool|int|float|string" for parameter type
Loading history...
344
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
345
     */
346 8
    final public static function hasValue($value)
347
    {
348 8
        $constants = self::detectConstants(static::class);
349 8
        return \in_array($value, $constants, true);
350
    }
351
352
    /**
353
     * Test if the given enumerator name is part of this enumeration
354
     *
355
     * @param string $name
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
356
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
357
     */
358 4
    final public static function hasName($name)
359
    {
360 4
        return \is_string($name) && \defined("static::$name");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
361
    }
362
363
    /**
364
     * Detect all public available constants of given enumeration class
365
     *
366
     * @param string $class
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
367
     * @return array
368
     */
369 406
    private static function detectConstants($class)
370
    {
371 406
        if (!isset(self::$constants[$class])) {
372 166
            $reflection = new ReflectionClass($class);
373 166
            $publicConstants  = [];
374
375
            do {
376 166
                $scopeConstants = [];
377 166
                if (\PHP_VERSION_ID >= 70100 && method_exists(ReflectionClass::class, 'getReflectionConstants')) {
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 114 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...
378
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
379
                    // for enumerations we are only interested in public once.
380
                    // NOTE: HHVM > 3.26.2 still does not support private/protected constants.
381
                    //       It allows the visibility keyword but ignores it.
382 126
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
383 123
                        if ($reflConstant->isPublic()) {
384 125
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
385
                        }
386
                    }
387
                } else {
388
                    // In PHP < 7.1 all class constants were public by definition
389 40
                    $scopeConstants = $reflection->getConstants();
390
                }
391
392 166
                $publicConstants = $scopeConstants + $publicConstants;
393 166
            } 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...
394
395 166
            assert(
396 166
                self::noAmbiguousValues($publicConstants),
397 166
                "Ambiguous enumerator values detected for {$class}"
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $class instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
398
            );
399
400 158
            self::$constants[$class] = $publicConstants;
401 158
            self::$names[$class] = \array_keys($publicConstants);
402
        }
403
404 398
        return self::$constants[$class];
405
    }
406
407
    /**
408
     * Test that the given constants does not contain ambiguous values
409
     * @param array $constants
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
410
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
411
     */
412 166
    private static function noAmbiguousValues(array $constants)
413
    {
414 166
        foreach ($constants as $value) {
415 162
            $names = \array_keys($constants, $value, true);
416 162
            if (\count($names) > 1) {
417 125
                return false;
418
            }
419
        }
420
421 150
        return true;
422
    }
423
424
    /**
425
     * Get an enumerator instance by the given name.
426
     *
427
     * This will be called automatically on calling a method
428
     * with the same name of a defined enumerator.
429
     *
430
     * @param string $method The name of the enumerator (called as method)
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
431
     * @param array  $args   There should be no arguments
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
432
     * @return static
433
     * @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...
434
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
435
     */
436 108
    final public static function __callStatic($method, array $args)
437
    {
438 108
        return self::byName($method);
439
    }
440
}
441