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

Enum::resolveFromStaticMethods()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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