Completed
Push — master ( 06b09b...b18980 )
by Brent
20s
created

Enum::__call()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 4
nc 3
nop 2
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
12
abstract class Enum implements JsonSerializable
13
{
14
    /** @var array */
15
    protected static $cache = [];
16
17
    /** @var array */
18
    protected static $map = [];
19
20
    /** @var string */
21
    protected $value;
22
23
    /**
24
     * @param string $value
25
     *
26
     * @return static
27
     */
28
    public static function from(string $value): Enum
29
    {
30
        if (method_exists(static::class, $value)) {
31
            return forward_static_call(static::class.'::'.$value);
32
        }
33
34
        return new static($value);
35
    }
36
37
    public function __construct(string $value = null)
38
    {
39
        if ($value === null) {
40
            $value = $this->resolveValueFromStaticCall();
41
        }
42
43
        $enumValues = self::resolve();
44
45
        if (isset($enumValues[strtolower($value)])) {
46
            $value = $enumValues[strtolower($value)];
47
        }
48
49
        if (! in_array($value, $enumValues)) {
50
            throw new TypeError("Value {$value} not available in enum ".static::class);
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with "Value {$value} not avai... enum " . static::class.

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...
51
        }
52
53
        if ($value === null) {
54
            throw new TypeError("Value of enum can't be null");
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with 'Value of enum can\'t be null'.

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...
55
        }
56
57
        $this->value = $value;
58
    }
59
60
    public static function __callStatic($name, $arguments)
61
    {
62
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
63
            if (! isset($arguments[0])) {
64
                throw new \ArgumentCountError(sprintf('Calling %s::%s() in static context requires one argument', static::class, $name));
65
            }
66
67
            return static::from($arguments[0])->$name();
68
        }
69
70
        if (! isset(self::resolve()[strtolower($name)])) {
71
            throw new TypeError("Method {$name} not available in enum ".static::class);
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with "Method {$name} not avai... enum " . static::class.

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...
72
        }
73
74
        return new static($name);
75
    }
76
77
    public function __call($name, $arguments)
78
    {
79
        if (strlen($name) > 2 && strpos($name, 'is') === 0) {
80
            return $this->equals(substr($name, 2));
81
        }
82
83
        if (isset(self::resolve()[strtolower($name)])) {
84
            return static::__callStatic($name, $arguments);
85
        }
86
87
        throw new \BadMethodCallException(sprintf('Call to undefined method %s->%s()', static::class, $name));
88
    }
89
90
    /**
91
     * @param string|\Spatie\Enum\Enum $enum
92
     *
93
     * @return bool
94
     */
95
    public function equals($enum): bool
96
    {
97
        if (is_string($enum)) {
98
            $enum = static::from($enum);
99
        }
100
101
        if (! $enum instanceof $this) {
102
            return false;
103
        }
104
105
        if ($enum->value !== $this->value) {
106
            return false;
107
        }
108
109
        return true;
110
    }
111
112
    /**
113
     * @param string[]|\Spatie\Enum\Enum[] $enums
114
     *
115
     * @return bool
116
     */
117
    public function isOneOf(array $enums): bool
118
    {
119
        foreach ($enums as $enum) {
120
            if ($this->equals($enum)) {
121
                return true;
122
            }
123
        }
124
125
        return false;
126
    }
127
128
    public function __toString(): string
129
    {
130
        return $this->value;
131
    }
132
133
    public function jsonSerialize()
134
    {
135
        return $this->value;
136
    }
137
138
    public static function toArray(): array
139
    {
140
        return self::resolve();
141
    }
142
143
    public static function getKeys(): array
144
    {
145
        return array_keys(self::resolve());
146
    }
147
148
    public static function getValues(): array
149
    {
150
        return array_values(self::resolve());
151
    }
152
153
    protected static function resolve(): array
154
    {
155
        $enumValues = [];
156
157
        $class = static::class;
158
159
        if (isset(self::$cache[$class])) {
160
            return self::$cache[$class];
161
        }
162
163
        $staticReflection = new ReflectionClass(static::class);
164
165
        foreach (self::resolveValuesFromStaticMethods($staticReflection) as $value => $name) {
166
            $enumValues[$value] = $name;
167
        }
168
169
        foreach (self::resolveFromDocblocks($staticReflection) as $value => $name) {
170
            $enumValues[$value] = $name;
171
        }
172
173
        return self::$cache[$class] = $enumValues;
174
    }
175
176
    protected static function resolveValuesFromStaticMethods(ReflectionClass $staticReflection): array
177
    {
178
        $enumValues = [];
179
        foreach ($staticReflection->getMethods(ReflectionMethod::IS_STATIC) as $method) {
180
            if ($method->getDeclaringClass()->getName() === self::class) {
181
                continue;
182
            }
183
184
            $methodName = $method->getName();
185
            $enumValues[strtolower($methodName)] = static::$map[$methodName] ?? $methodName;
186
        }
187
188
        return $enumValues;
189
    }
190
191
    protected static function resolveFromDocblocks(ReflectionClass $staticReflection): array
192
    {
193
        $enumValues = [];
194
195
        $docComment = $staticReflection->getDocComment();
196
197
        if (! $docComment) {
198
            return $enumValues;
199
        }
200
201
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
202
203
        foreach ($matches[1] ?? [] as $valueName) {
204
            $enumValues[strtolower($valueName)] = static::$map[$valueName] ?? $valueName;
205
        }
206
207
        return $enumValues;
208
    }
209
210
    protected function resolveValueFromStaticCall(): ?string
211
    {
212
        if (strpos(get_class($this), 'class@anonymous') === 0) {
213
            $backtrace = debug_backtrace();
214
215
            return $backtrace[2]['function'];
216
        }
217
218
        return null;
219
    }
220
}
221