Completed
Pull Request — master (#18)
by Tom
30:19
created

Enum::getIndices()   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 Spatie\Enum\Exceptions\DuplicatedIndexException;
8
use Spatie\Enum\Exceptions\DuplicatedValueException;
9
use TypeError;
10
use ReflectionClass;
11
use JsonSerializable;
12
use ReflectionMethod;
13
use BadMethodCallException;
14
use Spatie\Enum\Exceptions\InvalidIndexException;
15
use Spatie\Enum\Exceptions\InvalidValueException;
16
17
abstract class Enum implements Enumerable, JsonSerializable
18
{
19
    /** @var array[] */
20
    protected static $cache = [];
21
22
    /** @var string */
23
    protected $value;
24
25
    /** @var int */
26
    protected $index;
27
28
    public function __construct(?string $value = null, ?int $index = null)
29
    {
30
        if (is_null($value) && is_null($index)) {
31
            $value = $this->resolveValueFromStaticCall();
32
            $index = static::toArray()[$value];
33
        }
34
35
        if (is_null($value) || ! static::isValidValue($value)) {
36
            throw new InvalidValueException($value, static::class);
37
        }
38
39
        if (is_null($index) || ! static::isValidIndex($index)) {
40
            throw new InvalidIndexException($index, static::class);
41
        }
42
43
        $this->value = $value;
44
        $this->index = $index;
45
    }
46
47
    public function __call($name, $arguments)
48
    {
49
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
50
            return $this->isEqual(substr($name, 2));
51
        }
52
53
        throw new BadMethodCallException(sprintf('Call to undefined method %s->%s()', static::class, $name));
54
    }
55
56
    public static function __callStatic($name, $arguments)
57
    {
58
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
59
            if (! isset($arguments[0])) {
60
                throw new \ArgumentCountError(sprintf('Calling %s::%s() in static context requires one argument', static::class, $name));
61
            }
62
63
            return static::make($arguments[0])->$name();
64
        }
65
66
        if (static::isValidName($name) || static::isValidValue($name)) {
67
            return static::make($name);
68
        }
69
70
        throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $name));
71
    }
72
73
    public static function make($value): Enumerable
74
    {
75
        if (! (is_int($value) || is_string($value))) {
76
            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...
77
        }
78
79
        $name = null;
80
        $index = null;
81
82
        if (is_int($value)) {
83
            if (! static::isValidIndex($value)) {
84
                throw new InvalidIndexException($value, static::class);
85
            }
86
87
            $name = array_combine(static::getIndices(), array_keys(static::resolve()))[$value];
88
            $index = $value;
89
            $value = array_search($index, static::toArray());
90
        } elseif (is_string($value)) {
91
            if (method_exists(static::class, $value)) {
92
                return forward_static_call(static::class.'::'.$value);
93
            }
94
95
            if (static::isValidValue($value)) {
96
                $index = static::toArray()[$value];
97
                $name = array_combine(static::getValues(), array_keys(static::resolve()))[$value];
98
            } elseif (static::isValidName($value)) {
99
                $name = $value;
100
                list('value' => $value, 'index' => $index) = static::resolve()[strtoupper($name)];
101
            }
102
        }
103
104
        if (is_string($name) && method_exists(static::class, $name)) {
105
            return forward_static_call(static::class.'::'.$name);
106
        } elseif (is_int($index) && is_string($value)) {
107
            return new static($value, $index);
108
        }
109
110
        throw new InvalidValueException($value, static::class);
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_column(static::resolve(), 'index');
131
    }
132
133
    public static function getValues(): array
134
    {
135
        return array_column(static::resolve(), 'value');
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_int($value) || 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 resolve(): array
196
    {
197
        $values = [];
198
199
        $class = static::class;
200
201
        if (isset(self::$cache[$class])) {
202
            return self::$cache[$class];
203
        }
204
205
        self::$cache[$class] = [];
206
207
        $reflection = new ReflectionClass(static::class);
208
209
        foreach (self::resolveFromDocBlocks($reflection) as $value) {
210
            $values[] = $value;
211
        }
212
213
        foreach (self::resolveFromStaticMethods($reflection) as $value) {
214
            $values[] = $value;
215
        }
216
217
        foreach ($values as $index => $value) {
218
            self::$cache[$class][strtoupper($value)] = [
219
                'index' => $index,
220
                'value' => $value,
221
            ];
222
        }
223
224
        foreach (self::$cache[$class] as $name => $enum) {
225
            self::$cache[$class][$name]['value'] = static::make($name)->getValue();
226
            self::$cache[$class][$name]['index'] = static::make($name)->getIndex();
227
        }
228
229
        $duplicatedValues = array_filter(array_count_values(static::getValues()), function(int $count) {
230
            return $count > 1;
231
        });
232
        $duplicatedIndices = array_filter(array_count_values(static::getIndices()), function(int $count) {
233
            return $count > 1;
234
        });
235
236
        if (! empty($duplicatedValues)) {
237
            throw new DuplicatedValueException(array_keys($duplicatedValues), static::class);
238
        }
239
240
        if (! empty($duplicatedIndices)) {
241
            throw new DuplicatedIndexException(array_keys($duplicatedIndices), static::class);
242
        }
243
244
        return self::$cache[$class];
245
    }
246
247
    protected static function resolveFromStaticMethods(ReflectionClass $reflection): array
248
    {
249
        $values = [];
250
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) {
251
            if ($method->getDeclaringClass()->getName() === self::class) {
252
                continue;
253
            }
254
255
            $values[] = $method->getName();
256
        }
257
258
        return $values;
259
    }
260
261
    protected static function resolveFromDocBlocks(ReflectionClass $reflection): array
262
    {
263
        $values = [];
264
265
        $docComment = $reflection->getDocComment();
266
267
        if (! $docComment) {
268
            return $values;
269
        }
270
271
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
272
273
        foreach ($matches[1] ?? [] as $value) {
274
            $values[] = $value;
275
        }
276
277
        return $values;
278
    }
279
280
    protected function resolveValueFromStaticCall(): string
281
    {
282
        $name = null;
283
284
        if (strpos(get_class($this), 'class@anonymous') === 0) {
285
            $backtrace = debug_backtrace();
286
287
            $name = $backtrace[2]['function'];
288
289
            if (static::isValidName($name)) {
290
                return static::resolve()[strtoupper($name)]['value'];
291
            }
292
        }
293
294
        throw new InvalidValueException($name, static::class);
295
    }
296
}
297