Passed
Push — master ( 13a545...f70274 )
by Константин
02:22
created

Enum   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Test Coverage

Coverage 88.42%

Importance

Changes 0
Metric Value
dl 0
loc 268
ccs 84
cts 95
cp 0.8842
rs 8.6
c 0
b 0
f 0
wmc 37

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A __toString() 0 3 1
C __call() 0 33 7
A getReadableName() 0 3 1
A in() 0 3 1
A getPropertyValue() 0 3 1
A unserialize() 0 3 1
B __callStatic() 0 28 5
A serialize() 0 3 1
A getId() 0 3 1
A getName() 0 4 1
A getProperty() 0 16 3
A is() 0 3 1
A toArray() 0 3 1
B getReflection() 0 22 5
A all() 0 13 3
A eq() 0 3 2
1
<?php
2
3
namespace Grachevko\Enum;
4
5
use BadMethodCallException;
6
use InvalidArgumentException;
7
use LogicException;
8
use ReflectionClass;
9
10
/**
11
 * @author Konstantin Grachev <[email protected]>
12
 */
13
abstract class Enum implements \Serializable
14
{
15
    /**
16
     * @var int
17
     */
18
    private $id;
19
20
    /**
21
     * @var ReflectionClass[]
22
     */
23
    private static $reflections = [];
24
25
    /**
26
     * @param int $id
27
     *
28
     * @throws InvalidArgumentException
29
     */
30 20
    public function __construct(int $id)
31
    {
32 20
        if (!\in_array($id, self::getReflection()->getConstants(), true)) {
33 1
            throw new InvalidArgumentException(sprintf('Undefined enum "%s" of class "%s"', $id, static::class));
34
        }
35
36 17
        $this->id = $id;
37 17
    }
38
39
    /**
40
     * @return string
41
     */
42
    public function __toString(): string
43
    {
44
        return (string) $this->getId();
45
    }
46
47
    /**
48
     * @param string $name
49
     * @param array  $arguments
50
     *
51
     * @throws BadMethodCallException
52
     * @throws InvalidArgumentException
53
     * @throws LogicException
54
     *
55
     * @return bool|string
56
     */
57 7
    public function __call(string $name, array $arguments)
58
    {
59 7
        $id = $this->getId();
60 7
        $reflectionClass = self::getReflection();
61 7
        $constants = $reflectionClass->getConstants();
62
63 7
        if (0 === strpos($name, 'is') && ctype_upper($name[2])) {
64 4
            $const = Utils::stringToConstant(substr($name, 2, \strlen($name)));
65
66 4
            if (!array_key_exists($const, $constants)) {
67 1
                throw new InvalidArgumentException(sprintf(
68 1
                        'Undefined constant "%s" in class "%s" to use method "%s"', $const, static::class, $name)
69
                );
70
            }
71
72 4
            return $this->eq(new static($constants[$const]));
73
        }
74
75 3
        if (0 === strpos($name, 'get') && ctype_upper($name[3])) {
76 3
            $property = lcfirst(substr($name, 3));
77
78 3
            if ($value = $this->getPropertyValue($property)) {
79 2
                return $value;
80
            }
81
82 1
            throw new LogicException(sprintf(
83 1
                'Undefined value in property "%s" for "%s" constant',
84 1
                $property,
85 1
                array_flip($constants)[$id]
86
            ));
87
        }
88
89
        throw new BadMethodCallException(sprintf('Undefined method "%s" in class "%s"', $name, static::class));
90
    }
91
92
    /**
93
     * @param string $name
94
     * @param array  $arguments
95
     *
96
     * @throws BadMethodCallException
97
     * @throws LogicException
98
     *
99
     * @return static
100
     */
101 16
    public static function __callStatic(string $name, array $arguments)
102
    {
103 16
        $reflectionClass = self::getReflection();
104
105 16
        $const = Utils::stringToConstant($name);
106 16
        $constants = $reflectionClass->getConstants();
107
108 16
        if (array_key_exists($const, $constants)) {
109 15
            return new static($constants[$const]);
110
        }
111
112 2
        if (0 === strpos($name, 'from') && ctype_upper($name[4])) {
113 1
            $property = lcfirst(substr($name, 4));
114
115 1
            $value = $arguments[0];
116 1
            $values = array_flip(self::getProperty($property));
117 1
            if (array_key_exists($value, $values)) {
118 1
                return new static($values[$value]);
119
            }
120
121
            throw new LogicException(sprintf(
122
                'Undefined value "%s" in property "%s"',
123
                $value,
124
                $property
125
            ));
126
        }
127
128 1
        throw new BadMethodCallException(sprintf('Undefined method "%s" in class "%s"', $name, static::class));
129
    }
130
131
    /**
132
     * @return int
133
     */
134 13
    public function getId(): int
135
    {
136 13
        return $this->id;
137
    }
138
139
    /**
140
     * @return string
141
     */
142 4
    public function getName(): string
143
    {
144 4
        return $this->getPropertyValue('name')
145 4
            ?? strtolower(array_flip(self::getReflection()->getConstants())[$this->getId()]);
146
    }
147
148
    /**
149
     * @return string
150
     */
151 4
    public function getReadableName(): string
152
    {
153 4
        return mb_convert_case(str_replace('_', ' ', $this->getName()), MB_CASE_TITLE);
154
    }
155
156
    /**
157
     * @param array $ids
158
     * @param bool  $reverse
159
     *
160
     * @return array
161
     */
162 1
    public static function all(array $ids = [], $reverse = false): array
163
    {
164 1
        $all = array_values(self::getReflection()->getConstants());
165
166 1
        if (!$ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
167 1
            $ids = $all;
168
        } else {
169 1
            $ids = $reverse ? array_diff($all, $ids) : $ids;
170
        }
171
172 1
        return array_map(function (int $id) {
173 1
            return new static($id);
174 1
        }, $ids);
175
    }
176
177
    /**
178
     * @param array $array
179
     *
180
     * @return bool
181
     */
182
    public function in(array $array): bool
183
    {
184
        return \in_array($this->getId(), $array, true);
185
    }
186
187
    /**
188
     * @param int $id
189
     *
190
     * @return bool
191
     */
192
    public function is(int $id): bool
193
    {
194
        return $this->getId() === $id;
195
    }
196
197
    /**
198
     * @param Enum $enum
199
     *
200
     * @return bool
201
     */
202 6
    public function eq(Enum $enum): bool
203
    {
204 6
        return $this instanceof $enum && $enum->getId() === $this->getId();
205
    }
206
207
    /**
208
     * @return string
209
     */
210 1
    public function serialize(): string
211
    {
212 1
        return (string) $this->getId();
213
    }
214
215
    /**
216
     * @param string $serialized
217
     */
218 1
    public function unserialize($serialized): void
219
    {
220 1
        $this->id = (int) $serialized;
221 1
    }
222
223
    /**
224
     * @return array
225
     */
226 1
    public function toArray(): array
227
    {
228 1
        return [$this->getId() => $this];
229
    }
230
231
    /**
232
     * @throws LogicException
233
     *
234
     * @return ReflectionClass
235
     */
236 20
    private static function getReflection(): ReflectionClass
237
    {
238 20
        $class = static::class;
239
240 20
        if (array_key_exists($class, self::$reflections)) {
241 17
            return self::$reflections[$class];
242
        }
243
244 7
        self::$reflections[$class] = $reflection = new ReflectionClass($class);
245
246 7
        $constants = $reflection->getConstants();
247 7
        if ([] === $constants) {
248 1
            throw new LogicException(sprintf('Class %s must define Constants', static::class));
249
        }
250
251 6
        foreach ($constants as $value) {
252 6
            if (!\is_int($value)) {
253 6
                throw new LogicException('All enum constants must be in integer type');
254
            }
255
        }
256
257 5
        return $reflection;
258
    }
259
260 8
    private static function getProperty(string $property): array
261
    {
262 8
        $reflectionClass = self::getReflection();
263
264 8
        $values = [];
265 8
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
266 5
            if ($reflectionProperty->getName() === $property) {
267 5
                $reflectionProperty->setAccessible(true);
268 5
                $values = $reflectionProperty->getValue();
269 5
                $reflectionProperty->setAccessible(false);
270
271 5
                break;
272
            }
273
        }
274
275 8
        return $values;
276
    }
277
278 7
    private function getPropertyValue(string $property)
279
    {
280 7
        return self::getProperty($property)[$this->getId()] ?? null;
281
    }
282
}
283