Completed
Pull Request — master (#18)
by Tom
03:07 queued 01:40
created

Enum::getValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spatie\Enum;
6
7
use TypeError;
8
use ReflectionClass;
9
use JsonSerializable;
10
use ReflectionMethod;
11
use BadMethodCallException;
12
use Spatie\Enum\Exceptions\InvalidIndexException;
13
use Spatie\Enum\Exceptions\InvalidValueException;
14
15
abstract class Enum implements Enumerable, JsonSerializable
16
{
17
    /** @var array[] */
18
    protected static $cache = [];
19
20
    /** @var bool[] */
21
    protected static $resolved = [];
22
23
    /** @var string */
24
    protected $value;
25
26
    /** @var int */
27
    protected $index;
28
29
    public function __construct(?string $value = null, ?int $index = null)
30
    {
31
        if (! static::isResolved()) {
32
            return;
33
        }
34
35
        if (is_null($value) && is_null($index)) {
36
            $value = $this->resolveValueFromStaticCall();
37
            $index = static::toArray()[$value];
38
        }
39
40
        if (! static::isValidValue($value)) {
41
            throw new InvalidValueException($value, static::class);
42
        }
43
44
        if (! static::isValidIndex($index)) {
45
            throw new InvalidIndexException($value, static::class);
46
        }
47
48
        $this->value = $value;
49
        $this->index = $index;
50
    }
51
52
    public function __call($name, $arguments)
53
    {
54
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
55
            return $this->isEqual(substr($name, 2));
56
        }
57
58
        if (static::isValidValue($name)) {
59
            return static::__callStatic($name, $arguments);
60
        }
61
62
        throw new BadMethodCallException(sprintf('Call to undefined method %s->%s()', static::class, $name));
63
    }
64
65
    public static function __callStatic($name, $arguments)
66
    {
67
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
68
            if (! isset($arguments[0])) {
69
                throw new \ArgumentCountError(sprintf('Calling %s::%s() in static context requires one argument', static::class, $name));
70
            }
71
72
            return static::make($arguments[0])->$name();
73
        }
74
75
        if (static::isValidName($name) || static::isValidValue($name)) {
76
            return static::make($name);
77
        }
78
79
        throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $name));
80
    }
81
82
    public static function make($value): Enumerable
83
    {
84
        if (is_int($value)) {
85
            $index = $value;
86
87
            if (! static::isValidIndex($index)) {
88
                throw new InvalidIndexException($value, static::class);
89
            }
90
91
            return new static(array_search($index, static::toArray()), $index);
92
        } elseif (is_string($value)) {
93
            if (method_exists(static::class, $value)) {
94
                return forward_static_call(static::class.'::'.$value);
95
            }
96
97
            if (static::isValidValue($value)) {
98
                return new static($value, static::toArray()[$value]);
99
            }
100
101
            if (static::isValidName($value)) {
102
                $value = strtoupper($value);
103
104
                return new static(static::resolve()[$value]['value'], static::resolve()[$value]['index']);
105
            }
106
107
            throw new InvalidValueException($value, static::class);
108
        }
109
110
        throw new TypeError(sprintf('%s::make() expects string|int as argument but %s given', static::class, gettype($value)));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('%s::make() expe...class, gettype($value)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
111
    }
112
113
    public static function isValidIndex(int $index): bool
114
    {
115
        return in_array($index, static::getIndices(), true);
116
    }
117
118
    public static function isValidName(string $value): bool
119
    {
120
        return in_array(strtoupper($value), array_keys(static::resolve()), true);
121
    }
122
123
    public static function isValidValue(string $value): bool
124
    {
125
        return in_array($value, static::getValues(), true);
126
    }
127
128
    public static function getIndices(): array
129
    {
130
        return array_values(static::toArray());
131
    }
132
133
    public static function getValues(): array
134
    {
135
        return array_keys(static::toArray());
136
    }
137
138
    public static function toArray(): array
139
    {
140
        $resolved = static::resolve();
141
142
        return array_combine(array_column($resolved, 'value'), array_column($resolved, 'index'));
143
    }
144
145
    public function getValue(): string
146
    {
147
        return $this->value;
148
    }
149
150
    public function getIndex(): int
151
    {
152
        return $this->index;
153
    }
154
155
    public function isEqual($value): bool
156
    {
157
        if (is_string($value)) {
158
            $enum = static::make($value);
159
        } elseif ($value instanceof $this) {
160
            $enum = $value;
161
        }
162
163
        if (
164
            isset($enum)
165
            && $enum instanceof $this
166
            && $enum->getValue() === $this->getValue()
167
        ) {
168
            return true;
169
        }
170
171
        return false;
172
    }
173
174
    public function isAny(array $values): bool
175
    {
176
        foreach ($values as $value) {
177
            if ($this->isEqual($value)) {
178
                return true;
179
            }
180
        }
181
182
        return false;
183
    }
184
185
    public function __toString(): string
186
    {
187
        return $this->getValue();
188
    }
189
190
    public function jsonSerialize()
191
    {
192
        return $this->getValue();
193
    }
194
195
    protected static function isResolved(): bool
196
    {
197
        if (! isset(self::$resolved[static::class])) {
198
            static::resolve();
199
        }
200
201
        return self::$resolved[static::class] ?: false;
202
    }
203
204
    protected static function resolve(): array
205
    {
206
        $values = [];
207
208
        $class = static::class;
209
210
        if (isset(self::$cache[$class])) {
211
            return self::$cache[$class];
212
        }
213
214
        self::$resolved[static::class] = false;
215
        self::$cache[$class] = [];
216
217
        $reflection = new ReflectionClass(static::class);
218
219
        foreach (self::resolveFromDocBlocks($reflection) as $value) {
220
            $values[] = $value;
221
        }
222
223
        foreach (self::resolveFromStaticMethods($reflection) as $value) {
224
            $values[] = $value;
225
        }
226
227
        foreach ($values as $index => $value) {
228
            self::$cache[$class][strtoupper($value)] = [
229
                'index' => $index,
230
                'value' => $value,
231
            ];
232
        }
233
234
        self::$resolved[$class] = true;
235
236
        foreach (self::$cache[$class] as $name => $enum) {
237
            self::$cache[$class][$name]['value'] = static::make($name)->getValue();
238
            self::$cache[$class][$name]['index'] = static::make($name)->getIndex();
239
        }
240
241
        return self::$cache[$class];
242
    }
243
244
    protected static function resolveFromStaticMethods(ReflectionClass $reflection): array
245
    {
246
        $values = [];
247
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) {
248
            if ($method->getDeclaringClass()->getName() === self::class) {
249
                continue;
250
            }
251
252
            $values[] = $method->getName();
253
        }
254
255
        return $values;
256
    }
257
258
    protected static function resolveFromDocBlocks(ReflectionClass $reflection): array
259
    {
260
        $values = [];
261
262
        $docComment = $reflection->getDocComment();
263
264
        if (! $docComment) {
265
            return $values;
266
        }
267
268
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
269
270
        foreach ($matches[1] ?? [] as $value) {
271
            $values[] = $value;
272
        }
273
274
        return $values;
275
    }
276
277
    protected function resolveValueFromStaticCall(): string
278
    {
279
        $value = null;
280
281
        if (strpos(get_class($this), 'class@anonymous') === 0) {
282
            $backtrace = debug_backtrace();
283
284
            $value = $backtrace[2]['function'];
285
286
            if (static::isValidValue($value)) {
287
                return $value;
288
            }
289
        }
290
291
        throw new InvalidValueException($value, static::class);
292
    }
293
}
294