Completed
Pull Request — master (#21)
by Tom
02:08
created

Enum::getIndex()   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
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
218
        if (! empty($duplicatedValues)) {
219
            unset(self::$cache[$class]);
220
            throw new DuplicatedValueException(array_keys($duplicatedValues), static::class);
221
        }
222
223
        $duplicatedIndices = array_filter(array_count_values(static::getIndices()), function (int $count) {
224
            return $count > 1;
225
        });
226
227
        if (! empty($duplicatedIndices)) {
228
            unset(self::$cache[$class]);
229
            throw new DuplicatedIndexException(array_keys($duplicatedIndices), static::class);
230
        }
231
232
        return self::$cache[$class];
233
    }
234
235
    protected static function resolveFromDocBlocks(ReflectionClass $reflection): array
236
    {
237
        $values = [];
238
239
        $docComment = $reflection->getDocComment();
240
241
        if (! $docComment) {
242
            return $values;
243
        }
244
245
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
246
247
        foreach ($matches[1] ?? [] as $value) {
248
            $values[] = $value;
249
        }
250
251
        return $values;
252
    }
253
254
    protected static function resolveFromStaticMethods(ReflectionClass $reflection): array
255
    {
256
        $values = [];
257
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) {
258
            if ($method->getDeclaringClass()->getName() === self::class) {
259
                continue;
260
            }
261
262
            $values[] = $method->getName();
263
        }
264
265
        return $values;
266
    }
267
268
    protected function resolveByStaticCall(): array
269
    {
270
        if (strpos(get_class($this), 'class@anonymous') !== 0) {
271
            throw new InvalidValueException(null, static::class);
272
        }
273
274
        $backtrace = debug_backtrace();
275
276
        $name = $backtrace[2]['function'];
277
278
        if (! static::isValidName($name)) {
279
            throw new InvalidValueException($name, static::class);
280
        }
281
282
        return static::resolve()[strtoupper($name)];
283
    }
284
285
    protected static function resolveByIndex(int $index): array
286
    {
287
        $name = array_combine(static::getIndices(), array_keys(static::resolve()))[$index];
288
        $value = array_search($index, static::toArray());
289
290
        return [$name, $index, $value];
291
    }
292
293
    protected static function resolveByValue(string $value): array
294
    {
295
        $index = static::toArray()[$value];
296
        $name = array_combine(static::getValues(), array_keys(static::resolve()))[$value];
297
298
        return [$name, $index, $value];
299
    }
300
301
    protected static function resolveByName(string $name): array
302
    {
303
        list('value' => $value, 'index' => $index) = static::resolve()[strtoupper($name)];
304
305
        return [$name, $index, $value];
306
    }
307
}
308