Completed
Pull Request — master (#18)
by Tom
01:20
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
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 string */
21
    protected $value;
22
23
    /** @var int */
24
    protected $index;
25
26
    public function __construct(?string $value = null, ?int $index = null)
27
    {
28
        if (is_null($value) && is_null($index)) {
29
            $value = $this->resolveValueFromStaticCall();
30
            $index = static::toArray()[$value];
31
        }
32
33
        if (! static::isValidValue($value)) {
34
            throw new InvalidValueException($value, static::class);
35
        }
36
37
        if (! static::isValidIndex($index)) {
38
            throw new InvalidIndexException($value, static::class);
39
        }
40
41
        $this->value = $value;
42
        $this->index = $index;
43
    }
44
45
    public function __call($name, $arguments)
46
    {
47
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
48
            return $this->isEqual(substr($name, 2));
49
        }
50
51
        if (static::isValidValue($name)) {
52
            return static::__callStatic($name, $arguments);
53
        }
54
55
        throw new BadMethodCallException(sprintf('Call to undefined method %s->%s()', static::class, $name));
56
    }
57
58
    public static function __callStatic($name, $arguments)
59
    {
60
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
61
            if (! isset($arguments[0])) {
62
                throw new \ArgumentCountError(sprintf('Calling %s::%s() in static context requires one argument', static::class, $name));
63
            }
64
65
            return static::make($arguments[0])->$name();
66
        }
67
68
        if (static::isValidName($name) || static::isValidValue($name)) {
69
            return static::make($name);
70
        }
71
72
        throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $name));
73
    }
74
75
    public static function make($value): Enumerable
76
    {
77
        if (is_int($value)) {
78
            $index = $value;
79
80
            if (! static::isValidIndex($index)) {
81
                throw new InvalidIndexException($value, static::class);
82
            }
83
84
            return new static(array_search($index, static::toArray()), $index);
85
        } elseif (is_string($value)) {
86
            if (method_exists(static::class, $value)) {
87
                return forward_static_call(static::class.'::'.$value);
88
            }
89
90
            if (static::isValidValue($value)) {
91
                return new static($value, static::toArray()[$value]);
92
            }
93
94
            if (static::isValidName($value)) {
95
                $value = strtoupper($value);
96
97
                return new static(static::resolve()[$value]['value'], static::resolve()[$value]['index']);
98
            }
99
100
            throw new InvalidValueException($value, static::class);
101
        }
102
103
        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...
104
    }
105
106
    public static function isValidIndex(int $index): bool
107
    {
108
        return in_array($index, static::getIndices(), true);
109
    }
110
111
    public static function isValidName(string $value): bool
112
    {
113
        return in_array(strtoupper($value), array_keys(static::resolve()), true);
114
    }
115
116
    public static function isValidValue(string $value): bool
117
    {
118
        return in_array($value, static::getValues(), true);
119
    }
120
121
    public static function getIndices(): array
122
    {
123
        return array_values(static::toArray());
124
    }
125
126
    public static function getValues(): array
127
    {
128
        return array_keys(static::toArray());
129
    }
130
131
    public static function toArray(): array
132
    {
133
        $resolved = static::resolve();
134
135
        return array_combine(array_column($resolved, 'value'), array_column($resolved, 'index'));
136
    }
137
138
    public function getValue(): string
139
    {
140
        return $this->value;
141
    }
142
143
    public function getIndex(): int
144
    {
145
        return $this->index;
146
    }
147
148
    public function isEqual($value): bool
149
    {
150
        if (is_string($value)) {
151
            $enum = static::make($value);
152
        } elseif ($value instanceof $this) {
153
            $enum = $value;
154
        }
155
156
        if (
157
            isset($enum)
158
            && $enum instanceof $this
159
            && $enum->getValue() === $this->getValue()
160
        ) {
161
            return true;
162
        }
163
164
        return false;
165
    }
166
167
    public function isAny(array $values): bool
168
    {
169
        foreach ($values as $value) {
170
            if ($this->isEqual($value)) {
171
                return true;
172
            }
173
        }
174
175
        return false;
176
    }
177
178
    public function __toString(): string
179
    {
180
        return $this->getValue();
181
    }
182
183
    public function jsonSerialize()
184
    {
185
        return $this->getValue();
186
    }
187
188
    protected static function resolve(): array
189
    {
190
        $values = [];
191
192
        $class = static::class;
193
194
        if (isset(self::$cache[$class])) {
195
            return self::$cache[$class];
196
        }
197
198
        self::$cache[$class] = [];
199
200
        $reflection = new ReflectionClass(static::class);
201
202
        foreach (self::resolveFromDocBlocks($reflection) as $value) {
203
            $values[] = $value;
204
        }
205
206
        foreach (self::resolveFromStaticMethods($reflection) as $value) {
207
            $values[] = $value;
208
        }
209
210
        foreach ($values as $index => $value) {
211
            self::$cache[$class][strtoupper($value)] = [
212
                'index' => $index,
213
                'value' => $value,
214
            ];
215
        }
216
217
        foreach (self::$cache[$class] as $name => $enum) {
218
            self::$cache[$class][$name]['value'] = static::make($name)->getValue();
219
            self::$cache[$class][$name]['index'] = static::make($name)->getIndex();
220
        }
221
222
        return self::$cache[$class];
223
    }
224
225
    protected static function resolveFromStaticMethods(ReflectionClass $reflection): array
226
    {
227
        $values = [];
228
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) {
229
            if ($method->getDeclaringClass()->getName() === self::class) {
230
                continue;
231
            }
232
233
            $values[] = $method->getName();
234
        }
235
236
        return $values;
237
    }
238
239
    protected static function resolveFromDocBlocks(ReflectionClass $reflection): array
240
    {
241
        $values = [];
242
243
        $docComment = $reflection->getDocComment();
244
245
        if (! $docComment) {
246
            return $values;
247
        }
248
249
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
250
251
        foreach ($matches[1] ?? [] as $value) {
252
            $values[] = $value;
253
        }
254
255
        return $values;
256
    }
257
258
    protected function resolveValueFromStaticCall(): string
259
    {
260
        $value = null;
261
262
        if (strpos(get_class($this), 'class@anonymous') === 0) {
263
            $backtrace = debug_backtrace();
264
265
            $value = $backtrace[2]['function'];
266
267
            if (static::isValidValue($value)) {
268
                return $value;
269
            }
270
        }
271
272
        throw new InvalidValueException($value, static::class);
273
    }
274
}
275