Test Failed
Push — 2x-travis-versions ( 763642...67c1e3 )
by Marc
01:34
created

Enum   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Test Coverage

Coverage 95.16%

Importance

Changes 0
Metric Value
eloc 103
dl 0
loc 421
ccs 118
cts 124
cp 0.9516
rs 6.96
c 0
b 0
f 0
wmc 53

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 3 1
A __sleep() 0 3 1
A __construct() 0 4 1
A byValue() 0 17 5
A getNames() 0 3 1
A get() 0 7 3
A getEnumerators() 0 3 1
A getValue() 0 3 1
A __clone() 0 3 1
A byOrdinal() 0 19 3
A __wakeup() 0 3 1
A getConstants() 0 3 1
A byName() 0 14 3
A is() 0 8 5
A getByOrdinal() 0 3 1
A getOrdinals() 0 4 2
A getByName() 0 3 1
A getValues() 0 3 1
A has() 0 10 3
A getName() 0 3 1
A getOrdinal() 0 18 4
A clear() 0 4 1
B detectConstants() 0 47 10
A __callStatic() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Enum often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Enum, and based on these observations, apply Extract Interface, too.

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) 2015 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
     * An array of available constants by class
34
     *
35
     * @var array ["$class" => ["$name" => $value, ...], ...]
36
     */
37
    private static $constants = array();
1 ignored issue
show
Coding Style introduced by
Private member variable "constants" must contain a leading underscore
Loading history...
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
38
39
    /**
40
     * Already instantiated enumerators
41
     *
42
     * @var array ["$class" => ["$name" => $instance, ...], ...]
43
     */
44
    private static $instances = array();
1 ignored issue
show
Coding Style introduced by
Private member variable "instances" must contain a leading underscore
Loading history...
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
45
46
    /**
47
     * Constructor
48
     *
49
     * @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...
50
     * @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...
51
     */
52 16
    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...
53
    {
54 16
        $this->value   = $value;
55 16
        $this->ordinal = $ordinal;
56 16
    }
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
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
71
     *                        because instances are implemented as singletons
72
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
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
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
80
     *                        because instances are implemented as singletons
81
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
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
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
89
     *                        because instances are implemented as singletons
90
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
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
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...
100
     */
101 22
    final public function getValue()
102
    {
103 22
        return $this->value;
104
    }
105
106
    /**
107
     * Get the name of the enumerator
108
     *
109
     * @return string
110
     */
111 8
    final public function getName()
112
    {
113 8
        return array_search($this->value, self::detectConstants(get_called_class()), true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_search($thi..._called_class()), true) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
114
    }
115
116
    /**
117
     * Get the ordinal number of the enumerator
118
     *
119
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
120
     */
121 44
    final public function getOrdinal()
122
    {
123 44
        if ($this->ordinal !== null) {
124 43
            return $this->ordinal;
125
        }
126
127
        // detect ordinal
128 9
        $ordinal = 0;
129 9
        $value   = $this->value;
130 9
        foreach (self::detectConstants(get_called_class()) as $constValue) {
131 9
            if ($value === $constValue) {
132 9
                break;
133
            }
134 9
            ++$ordinal;
135 9
        }
136
137 9
        $this->ordinal = $ordinal;
138 9
        return $ordinal;
139
    }
140
141
    /**
142
     * Compare this enumerator against another and check if it's the same.
143
     *
144
     * @param mixed $enumerator
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
145
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
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
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...
152 2
            || ($enumerator instanceof static
153 2
                && get_class($enumerator) === get_called_class()
154 2
                && $enumerator->value === $this->value
155 2
            );
156
    }
157
158
    /**
159
     * Get an enumerator instance of the given value or instance
160
     *
161
     * @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...
162
     * @return static
163
     * @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...
164
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
165
     */
166 67
    final public static function get($value)
167
    {
168 67
        if ($value instanceof static && get_class($value) === get_called_class()) {
169 26
            return $value;
170
        }
171
172 48
        return static::byValue($value);
173
    }
174
175
    /**
176
     * Get an enumerator instance by the given value
177
     *
178
     * @param mixed $value
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
179
     * @return static
180
     * @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...
181
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
182
     */
183 48
    final public static function byValue($value)
184
    {
185 48
        $class     = get_called_class();
186 48
        $constants = self::detectConstants($class);
187 46
        $name      = array_search($value, $constants, true);
188 46
        if ($name === false) {
189 7
            $message = is_scalar($value)
190 7
                ? 'Unknown value ' . var_export($value, true)
1 ignored issue
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
191 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 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...
192 7
            throw new InvalidArgumentException($message);
193
        }
194
195 40
        if (!isset(self::$instances[$class][$name])) {
196 7
            self::$instances[$class][$name] = new $class($constants[$name]);
197 7
        }
198
199 40
        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
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
206
     * @return static
207
     * @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...
208
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
209
     */
210 37
    final public static function byName($name)
211
    {
212 37
        $name  = (string) $name;
213 37
        $class = get_called_class();
214 37
        if (isset(self::$instances[$class][$name])) {
215 31
            return self::$instances[$class][$name];
216
        }
217
218 8
        $const = $class . '::' . $name;
219 8
        if (!defined($const)) {
220 1
            throw new InvalidArgumentException($const . ' not defined');
221
        }
222
223 7
        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
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...
230
     * @return static
231
     * @throws InvalidArgumentException On an invalid ordinal number
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
232
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
233
     */
234 19
    final public static function byOrdinal($ordinal)
235
    {
236 19
        $ordinal   = (int) $ordinal;
237 19
        $class     = get_called_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 1
            ));
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 an enumerator instance by the given name
257
     *
258
     * @param string $name The name of the enumerator
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
259
     * @return static
260
     * @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...
261
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
262
     * @deprecated
263
     */
264
    final public static function getByName($name)
265
    {
266
        return static::byName($name);
267
    }
268
269
    /**
270
     * Get an enumeration instance by the given ordinal number
271
     *
272
     * @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...
273
     * @return static
274
     * @throws InvalidArgumentException On an invalid ordinal number
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
275
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
276
     * @deprecated
277
     */
278
    final public static function getByOrdinal($ordinal)
279
    {
280
        return static::byOrdinal($ordinal);
281
    }
282
283
    /**
284
     * Clear all instantiated enumerators of the called class
285
     *
286
     * NOTE: This can break singleton behavior ... use it with caution!
287
     *
288
     * @return void
289
     * @deprecated
290
     */
291
    final public static function clear()
292
    {
293
        $class = get_called_class();
294
        unset(self::$instances[$class], self::$constants[$class]);
295
    }
296
297
    /**
298
     * Get a list of enumerator instances ordered by ordinal number
299
     *
300
     * @return static[]
301
     */
302 13
    final public static function getEnumerators()
303
    {
304 13
        return array_map('self::byName', array_keys(self::detectConstants(get_called_class())));
305
    }
306
307
    /**
308
     * Get a list of enumerator values ordered by ordinal number
309
     *
310
     * @return mixed[]
311
     */
312 1
    final public static function getValues()
313
    {
314 1
        return array_values(self::detectConstants(get_called_class()));
315
    }
316
317
    /**
318
     * Get a list of enumerator names ordered by ordinal number
319
     *
320
     * @return string[]
321
     */
322 1
    final public static function getNames()
323
    {
324 1
        return array_keys(self::detectConstants(get_called_class()));
325
    }
326
    /*
327
     * Get a list of enumerator ordinal numbers
328
     *
329
     * @return int[]
330
     */
331 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...
332
    {
333 1
        $count = count(self::detectConstants(get_called_class()));
334 1
        return $count === 0 ? array() : range(0, $count - 1);
0 ignored issues
show
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
335
    }
336
337
    /**
338
     * Get all available constants of the called class
339
     *
340
     * @return array
341
     * @throws LogicException On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
342
     */
343 62
    final public static function getConstants()
344
    {
345 62
        return self::detectConstants(get_called_class());
346
    }
347
348
    /**
349
     * Is the given enumerator part of this enumeration
350
     * 
351
     * @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...
352
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
353
     */
354 1
    final public static function has($value)
355
    {
356 1
        if ($value instanceof static && get_class($value) === get_called_class()) {
357 1
            return true;
358
        }
359
360 1
        $class     = get_called_class();
361 1
        $constants = self::detectConstants($class);
362
363 1
        return in_array($value, $constants, true);
364
    }
365
366
    /**
367
     * Detect all available constants by the given class
368
     *
369
     * @param string $class
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
370
     * @return array
371
     * @throws LogicException On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
372
     */
373 82
    private static function detectConstants($class)
374
    {
375 82
        if (!isset(self::$constants[$class])) {
376 13
            $reflection = new ReflectionClass($class);
377 13
            $constants  = array();
0 ignored issues
show
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
378
379
            do {
380 13
                $scopeConstants = array();
0 ignored issues
show
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
381 13
                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...
382
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
383
                    // for enumerations we are only interested in public once.
384
                    // NOTE: HHVM > 3.26.2 still does not support private/protected constants.
385
                    //       It allows the visibility keyword but ignores it.
386
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
387
                        if ($reflConstant->isPublic()) {
388
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
389
                        }
390
                    }
391 13
                } else {
392
                    // In PHP < 7.1 all class constants were public by definition
393
                    $scopeConstants = $reflection->getConstants();
394 13
                }
395 13
396
                $constants = $scopeConstants + $constants;
397
            } 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...
398 13
399 13
            // Detect ambiguous values and report names
400 12
            $ambiguous = array();
0 ignored issues
show
Coding Style introduced by
Short array syntax must be used to define arrays
Loading history...
401 12
            foreach ($constants as $value) {
402 2
                $names = array_keys($constants, $value, true);
403 2
                if (count($names) > 1) {
404 13
                    $ambiguous[var_export($value, true)] = $names;
405 13
                }
406 2
            }
407
            if (!empty($ambiguous)) {
408 2
                throw new LogicException(
409 2
                    'All possible values needs to be unique. The following are ambiguous: '
410 2
                    . implode(', ', array_map(function ($names) use ($constants) {
411 2
                        return implode('/', $names) . '=' . var_export($constants[$names[0]], true);
412
                    }, $ambiguous))
413
                );
414 11
            }
415 11
416
            self::$constants[$class] = $constants;
417 80
        }
418
419
        return self::$constants[$class];
420
    }
421
422
    /**
423
     * Get an enumerator instance by the given name.
424
     *
425
     * This will be called automatically on calling a method
426
     * with the same name of a defined enumerator.
427
     *
428
     * @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...
429
     * @param array  $args   There should be no arguments
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
430
     * @return static
431
     * @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...
432 22
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
433
     */
434 22
    final public static function __callStatic($method, array $args)
435
    {
436
        return self::byName($method);
437
    }
438
}
439