Test Setup Failed
Push — master ( 51fe54...551c5d )
by Константин
04:03
created

Enum::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Grachevko\Enum;
4
5
use BadMethodCallException;
6
use function in_array;
7
use InvalidArgumentException;
8
use function is_int;
9
use LogicException;
10
use ReflectionClass;
11
use ReflectionException;
12
use ReflectionProperty;
13
use Serializable;
14
15
/**
16
 * @author Konstantin Grachev <[email protected]>
17
 */
18
abstract class Enum implements Serializable
19
{
20
    /**
21
     * @var int
22
     */
23
    private $id;
24
25
    /**
26
     * @var ReflectionClass[]
27
     */
28
    private static $reflections = [];
29
30 20
    /**
31
     * @var ReflectionProperty[][]
32 20
     */
33 1
    private static $properties = [];
34
35
    /**
36 17
     * @var Enum[][]
37 17
     */
38
    private static $instances = [];
39
40
    /**
41
     * @param int $id
42
     *
43
     * @throws InvalidArgumentException
44
     * @throws ReflectionException
45
     */
46
    private function __construct(int $id)
47
    {
48
        if (!in_array($id, self::getReflection()->getConstants(), true)) {
49
            throw new InvalidArgumentException(sprintf('Undefined enum "%s" of class "%s"', $id, static::class));
50
        }
51
52
        $this->id = $id;
53
    }
54
55
    /**
56
     * @return string
57 7
     */
58
    final public function __toString(): string
59 7
    {
60 7
        return (string) $this->getId();
61 7
    }
62
63 7
    /**
64 4
     * @param string $name
65
     * @param array  $arguments
66 4
     *
67 1
     * @throws ReflectionException
68 1
     * @throws InvalidArgumentException
69
     * @throws BadMethodCallException
70
     *
71
     * @return bool|string
72 4
     */
73
    final public function __call(string $name, array $arguments)
74
    {
75 3
        $reflection = self::getReflection();
76 3
77
        if (0 === strpos($name, 'is') && ctype_upper($name[2])) {
78 3
            $const = Utils::stringToConstant(substr($name, 2));
79 2
80
            if (!$reflection->hasConstant($const)) {
81
                throw new InvalidArgumentException(
82 1
                    sprintf('Undefined constant "%s" or method "%s" in class "%s"', $const, $name, static::class)
83 1
                );
84 1
            }
85 1
86
            return $this->eq(static::create($reflection->getConstant($const)));
87
        }
88
89
        $property = lcfirst(substr($name, 3));
90
        if (0 === strpos($name, 'get') && ctype_upper($name[3])) {
91
            if (!$reflection->hasProperty($property)) {
92
                throw new InvalidArgumentException(
93
                    sprintf('Undefined property "%s" or method "%s" in class "%s"', $property, $name, static::class)
94
                );
95
            }
96
97
            return $this->get($property);
98
        }
99
100
        throw new BadMethodCallException(sprintf('Undefined method "%s" in class "%s"', $name, static::class));
101 16
    }
102
103 16
    /**
104
     * @param string $name
105 16
     * @param array  $arguments
106 16
     *
107
     * @throws ReflectionException
108 16
     * @throws BadMethodCallException
109 15
     * @throws LogicException
110
     *
111
     * @return static
112 2
     */
113 1
    final public static function __callStatic(string $name, array $arguments)
114
    {
115 1
        $reflectionClass = self::getReflection();
116 1
117 1
        $const = Utils::stringToConstant($name);
118 1
        $constants = $reflectionClass->getConstants();
119
120
        if (array_key_exists($const, $constants)) {
121
            return static::create($constants[$const]);
122
        }
123
124
        if (0 === strpos($name, 'from') && ctype_upper($name[4])) {
125
            return static::from(lcfirst(substr($name, 4)), $arguments[0]);
126
        }
127
128 1
        throw new BadMethodCallException(sprintf('Undefined method "%s" in class "%s"', $name, static::class));
129
    }
130
131
    /**
132
     * @param int $id
133
     *
134 13
     * @throws ReflectionException
135
     *
136 13
     * @return static
137
     */
138
    final public static function create(int $id): self
139
    {
140
        return self::$instances[static::class][$id] ?? self::$instances[static::class][$id] = new static($id);
141
    }
142 4
143
    /**
144 4
     * @param string $property
145 4
     * @param mixed  $value
146
     *
147
     * @throws ReflectionException
148
     *
149
     * @return Enum
150
     */
151 4
    final public static function from(string $property, $value): self
152
    {
153 4
        return static::create(array_flip(self::$properties[static::class][$property]->getValue())[$value]);
154
    }
155
156
    /**
157
     * @return int
158
     */
159
    final public function getId(): int
160
    {
161
        return $this->id;
162 1
    }
163
164 1
    /**
165
     * @throws ReflectionException
166 1
     *
167 1
     * @return string
168
     */
169 1
    final public function getName(): string
170
    {
171
        if (self::getReflection()->hasProperty('name')) {
172
            return $this->get('name');
173 1
        }
174 1
175
        return strtolower(array_flip(self::getReflection()->getConstants())[$this->getId()]);
176
    }
177
178
    /**
179
     * @param string $property
180
     *
181
     * @return mixed
182
     */
183
    final public function get(string $property)
184
    {
185
        return self::$properties[static::class][$property]->getValue()[$this->getId()];
186
    }
187
188
    /**
189
     * @param array $ids
190
     * @param bool  $reverse
191
     *
192
     * @throws ReflectionException
193
     *
194
     * @return array
195
     */
196
    final public static function all(array $ids = [], $reverse = false): array
197
    {
198
        $all = array_values(self::getReflection()->getConstants());
199
200
        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...
201
            $ids = $all;
202 6
        } else {
203
            $ids = $reverse ? array_diff($all, $ids) : $ids;
204 6
        }
205
206
        return array_map(static function (int $id) {
207
            return static::create($id);
208
        }, $ids);
209
    }
210 1
211
    /**
212 1
     * @param array $array
213
     *
214
     * @return bool
215
     */
216
    final public function in(array $array): bool
217
    {
218 1
        return in_array($this->getId(), $array, true);
219
    }
220 1
221 1
    /**
222
     * @param int $id
223
     *
224
     * @return bool
225
     */
226 1
    final public function is(int $id): bool
227
    {
228 1
        return $this->getId() === $id;
229
    }
230
231
    /**
232
     * @param Enum $enum
233
     *
234
     * @return bool
235
     */
236 20
    final public function eq(Enum $enum): bool
237
    {
238 20
        return $this instanceof $enum && $enum->getId() === $this->getId();
239
    }
240 20
241 17
    /**
242
     * @return string
243
     */
244 7
    final public function serialize(): string
245
    {
246 7
        return (string) $this->getId();
247 7
    }
248 1
249
    /**
250
     * @param string $serialized
251 6
     */
252 6
    final public function unserialize($serialized): void
253 6
    {
254
        $this->id = (int) $serialized;
255
    }
256
257 5
    /**
258
     * @return array
259
     */
260 8
    final public function toArray(): array
261
    {
262 8
        return [$this->getId() => $this];
263
    }
264 8
265 8
    /**
266 5
     * @throws ReflectionException
267 5
     * @throws LogicException
268 5
     *
269 5
     * @return ReflectionClass
270
     */
271 5
    private static function getReflection(): ReflectionClass
272
    {
273
        $class = static::class;
274
275 8
        if (array_key_exists($class, self::$reflections)) {
276
            return self::$reflections[$class];
277
        }
278 7
279
        $reflection = new ReflectionClass($class);
280 7
281
        $constants = $reflection->getConstants();
282
283
        if ([] === $constants) {
284
            throw new LogicException(sprintf('Class %s must define Constants', static::class));
285
        }
286
287
        foreach ($reflection->getReflectionConstants() as $reflectionConstant) {
288
            if (false === $reflectionConstant->isPrivate()) {
289
                throw new LogicException('All constants must be private by design.');
290
            }
291
292
            if (!is_int($reflectionConstant->getValue())) {
293
                throw new LogicException('All enum constants must be type of integer by design.');
294
            }
295
        }
296
297
        foreach ($reflection->getProperties() as $property) {
298
            $property->setAccessible(true);
299
300
            self::$properties[static::class][$property->getName()] = $property;
301
302
            if ($property->isPublic()) {
303
                throw new LogicException('All properties must be private or protected by design.');
304
            }
305
306
            if (!$property->isStatic()) {
307
                throw new LogicException('All properties must be static by design.');
308
            }
309
310
            if (array_values($constants) !== array_keys($property->getValue())) {
311
                throw new LogicException('Properties must have values for all constants by design.');
312
            }
313
        }
314
315
        return self::$reflections[$class] = $reflection;
316
    }
317
}
318