GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#43)
by Tom
01:17
created

HasEnums::getEnumAttribute()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 6
cts 6
cp 1
rs 9.2728
c 0
b 0
f 0
cc 5
nc 4
nop 2
crap 5
1
<?php
2
3
namespace Spatie\Enum\Laravel;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Str;
8
use InvalidArgumentException;
9
use Spatie\Enum\Enumerable;
10
use Spatie\Enum\Laravel\Exceptions\InvalidEnumError;
11
use Spatie\Enum\Laravel\Exceptions\NoSuchEnumField;
12
13
/**
14
 * @mixin Model
15
 */
16
trait HasEnums
17 80
{
18
    public function setAttribute($key, $value)
19 80
    {
20 80
        return $this->isEnumAttribute($key)
21 72
            ? $this->setEnumAttribute($key, $value)
22
            : parent::setAttribute($key, $value);
23
    }
24 28
25
    public function getAttribute($key)
26 28
    {
27
        $value = parent::getAttribute($key);
28 28
29 28
        return $this->isEnumAttribute($key)
30 28
            ? $this->getEnumAttribute($key, $value)
31
            : $value;
32
    }
33
34
    /**
35
     * @param \Illuminate\Database\Eloquent\Builder $builder
36
     * @param string $key
37
     * @param int|string|\Spatie\Enum\Enumerable|int[]|string[]|\Spatie\Enum\Enumerable[] $enumerables
38
     *
39
     * @see \Illuminate\Database\Eloquent\Builder::whereIn()
40 32
     */
41
    public function scopeWhereEnum(
42
        Builder $builder,
43
        string $key,
44
        $enumerables
45 32
    ): void {
46 32
        $this->buildEnumScope(
47 32
            $builder,
48 16
            'whereIn',
49 16
            $key,
50
            $enumerables
51 28
        );
52
    }
53
54
    /**
55
     * @param \Illuminate\Database\Eloquent\Builder $builder
56
     * @param string $key
57
     * @param int|string|\Spatie\Enum\Enumerable|int[]|string[]|\Spatie\Enum\Enumerable[] $enumerables
58
     *
59
     * @see \Illuminate\Database\Eloquent\Builder::orWhereIn()
60 12
     */
61
    public function scopeOrWhereEnum(
62
        Builder $builder,
63
        string $key,
64
        $enumerables
65 12
    ): void {
66 12
        $this->buildEnumScope(
67 12
            $builder,
68 6
            'orWhereIn',
69 6
            $key,
70
            $enumerables
71 8
        );
72
    }
73
74
    /**
75
     * @param \Illuminate\Database\Eloquent\Builder $builder
76
     * @param string $key
77
     * @param int|string|\Spatie\Enum\Enumerable|int[]|string[]|\Spatie\Enum\Enumerable[] $enumerables
78
     *
79
     * @see \Illuminate\Database\Eloquent\Builder::whereNotIn()
80 20
     */
81
    public function scopeWhereNotEnum(
82
        Builder $builder,
83
        string $key,
84
        $enumerables
85 20
    ): void {
86 20
        $this->buildEnumScope(
87 20
            $builder,
88 10
            'whereNotIn',
89 10
            $key,
90
            $enumerables
91 16
        );
92
    }
93
94
    /**
95
     * @param \Illuminate\Database\Eloquent\Builder $builder
96
     * @param string $key
97
     * @param int|string|\Spatie\Enum\Enumerable|int[]|string[]|\Spatie\Enum\Enumerable[] $enumerables
98
     *
99
     * @see \Illuminate\Database\Eloquent\Builder::orWhereNotIn()
100 12
     */
101
    public function scopeOrWhereNotEnum(
102
        Builder $builder,
103
        string $key,
104
        $enumerables
105 12
    ): void {
106 12
        $this->buildEnumScope(
107 12
            $builder,
108 6
            'orWhereNotIn',
109 6
            $key,
110
            $enumerables
111 8
        );
112
    }
113
114
    /**
115
     * @param string $key
116
     * @param int|int[]|string|string[]|\Spatie\Enum\Enumerable|\Spatie\Enum\Enumerable[] $value
117
     *
118
     * @return $this
119 80
     */
120
    protected function setEnumAttribute(string $key, $value)
121 80
    {
122
        $enumClass = $this->getEnumClass($key);
123 76
124 16
        if (is_null($value)) {
125
            if (! $this->isNullableEnum($key)) {
126
                $enumInterface = Enumerable::class;
127 76
                throw new InvalidArgumentException("{$enumInterface} {$enumClass} is not nullable");
128 4
            }
129
130
            $this->attributes[$key] = null;
0 ignored issues
show
Bug introduced by
The property attributes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
131 72
132
            return $this;
133 72
        }
134
135
        if ($this->isArrayOfEnums($key)) {
136
            if(!is_array($value)) {
137
                $modelClass = static::class;
138
                throw new InvalidArgumentException("{$modelClass} [{$key}] expects array of enum values.");
139
            }
140
141
            return parent::setAttribute($key, array_map(function($value) use ($key, $enumClass) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (setAttribute() instead of setEnumAttribute()). Are you sure this is correct? If so, you might want to change this to $this->setAttribute().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
142 72
                return $this->getStoredValue($key, $this->asEnum($enumClass, $value));
143
            }, $value));
144 72
        }
145 4
146 72
        if (is_string($value) || is_int($value)) {
147
            $value = $this->asEnum($enumClass, $value);
148
        }
149
150
        if (! is_a($value, $enumClass)) {
151
            throw InvalidEnumError::make(static::class, $key, $enumClass, get_class($value));
152
        }
153
154
        $this->attributes[$key] = $this->getStoredValue($key, $value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 120 can also be of type array<integer,integer> or array<integer,object<Spatie\Enum\Enumerable>> or array<integer,string>; however, Spatie\Enum\Laravel\HasEnums::getStoredValue() does only seem to accept object<Spatie\Enum\Enumerable>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
155 72
156
        return $this;
157 72
    }
158
159
    /**
160 96
     * @param string $key
161
     * @param \Spatie\Enum\Enumerable $enum
162 96
     *
163
     * @return int|string
164
     */
165 80
    protected function getStoredValue(string $key, Enumerable $enum)
166
    {
167 80
        return $this->hasCast($key, ['int', 'integer'])
0 ignored issues
show
Bug introduced by
It seems like hasCast() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
168 80
            ? $enum->getIndex()
169 80
            : $enum->getValue();
170
    }
171 80
172 4
    /**
173
     * @param string $key
174
     * @param int|int[]|string|string[]|null $value
175 76
     *
176
     * @return \Spatie\Enum\Enumerable|\Spatie\Enum\Enumerable[]|null
177
     */
178
    protected function getEnumAttribute(string $key, $value)
179
    {
180
        if (is_null($value) && $this->isNullableEnum($key)) {
181
            return null;
182
        }
183
184 72
        $enumClass = $this->getEnumClass($key);
185
186 72
        if ($this->isArrayOfEnums($key)) {
187 36
            if(!is_array($value)) {
188
                $modelClass = static::class;
189
                throw new InvalidArgumentException("{$modelClass} [{$key}] expects array of enum values.");
190 40
            }
191 40
192 20
            return array_map(function($value) use ($enumClass): Enumerable {
193
                return $this->asEnum($enumClass, $value);
194
            }, $value);
195
        }
196
197
        return $this->asEnum($enumClass, $value);
198
    }
199
200
    protected function isEnumAttribute(string $key): bool
201
    {
202 60
        return isset($this->enums[$key]);
0 ignored issues
show
Bug introduced by
The property enums does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
203
    }
204
205
    protected function getEnumCast(string $key): array
206
    {
207
        $enumCast = $this->enums[$key];
208 60
209 16
        if (! Str::contains($enumCast, ':')) {
210
            return [$enumCast, []];
211
        }
212 44
213
        [$enumClass, $options] = explode(':', $enumCast);
0 ignored issues
show
Bug introduced by
The variable $enumClass does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $options seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
214 44
215 44
        $options = explode(',', $options);
0 ignored issues
show
Bug introduced by
The variable $options seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
216
217 44
        return [$enumClass, $options];
218 44
    }
219
220 44
    protected function isNullableEnum(string $key): bool
221
    {
222
        [, $options] = $this->getEnumCast($key);
0 ignored issues
show
Bug introduced by
The variable $options does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
223
224
        return in_array('nullable', $options);
225
    }
226
227
    protected function isArrayOfEnums(string $key): bool
228
    {
229
        [, $options] = $this->getEnumCast($key);
0 ignored issues
show
Bug introduced by
The variable $options does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
230
231
        return in_array('array', $options);
232
    }
233
234
    protected function getEnumClass(string $key): string
235
    {
236
        [$enumClass] = $this->getEnumCast($key);
0 ignored issues
show
Bug introduced by
The variable $enumClass does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
237
238
        $enumInterface = Enumerable::class;
239
240
        $classImplementsEnumerable = class_implements($enumClass)[$enumInterface] ?? false;
241
242
        if (! $classImplementsEnumerable) {
243
            throw new InvalidArgumentException("Expected {$enumClass} to implement {$enumInterface}");
244
        }
245
246
        return $enumClass;
247
    }
248
249
    /**
250
     * @param string $class
251
     * @param int|string $value
252
     *
253
     * @return \Spatie\Enum\Enumerable
254
     */
255
    protected function asEnum(string $class, $value): Enumerable
256
    {
257
        if ($value instanceof Enumerable) {
258
            return $value;
259
        }
260
261
        return forward_static_call(
262
            $class.'::make',
263
            $value
264
        );
265
    }
266
267
    /**
268
     * @param \Illuminate\Database\Eloquent\Builder $builder
269
     * @param string $method
270
     * @param string $key
271
     * @param int|string|\Spatie\Enum\Enumerable|int[]|string[]|\Spatie\Enum\Enumerable[] $enumerables
272
     */
273
    protected function buildEnumScope(
274
        Builder $builder,
275
        string $method,
276
        string $key,
277
        $enumerables
278
    ): void {
279
        if (! $this->isEnumAttribute($key)) {
280
            throw NoSuchEnumField::make($key, get_class($this));
281
        }
282
283
        $enumerables = is_array($enumerables) ? $enumerables : [$enumerables];
284
285
        $builder->$method(
286
            $key,
287
            array_map(function ($value) use ($key) {
288
                return $this->getStoredValue($key, $this->getEnumAttribute($key, $value));
0 ignored issues
show
Bug introduced by
It seems like $this->getEnumAttribute($key, $value) targeting Spatie\Enum\Laravel\HasEnums::getEnumAttribute() can also be of type array or null; however, Spatie\Enum\Laravel\HasEnums::getStoredValue() does only seem to accept object<Spatie\Enum\Enumerable>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
289
            }, $enumerables)
290
        );
291
    }
292
}
293