Completed
Pull Request — master (#21)
by Tom
03:15 queued 41s
created

Enum::resolveByName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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