Completed
Pull Request — master (#18)
by Tom
16:29 queued 05:33
created

Enum::isOneOf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 1

1 Method

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