Completed
Pull Request — master (#1449)
by
unknown
01:21
created

HasRoles::getArgumentRoles()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 1
nop 2
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Spatie\Permission\Traits;
4
5
use Illuminate\Support\Collection;
6
use Spatie\Permission\Contracts\Role;
7
use Illuminate\Database\Eloquent\Builder;
8
use Spatie\Permission\PermissionRegistrar;
9
use Illuminate\Database\Eloquent\Relations\MorphToMany;
10
11
trait HasRoles
12
{
13
    use HasPermissions;
14
15
    public static function bootHasRoles()
16
    {
17
        static::deleting(function ($model) {
18
            if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
19
                return;
20
            }
21
22
            $model->roles()->detach();
23
        });
24
    }
25
26
    /**
27
     * A model may have multiple roles.
28
     */
29
    public function roles(): MorphToMany
30
    {
31
        return $this->morphToMany(
0 ignored issues
show
Bug introduced by
It seems like morphToMany() 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...
32
            config('permission.models.role'),
33
            'model',
34
            config('permission.table_names.model_has_roles'),
35
            config('permission.column_names.model_morph_key'),
36
            'role_id'
37
        );
38
    }
39
40
    /**
41
     * Scope the model query to certain roles only.
42
     *
43
     * @param \Illuminate\Database\Eloquent\Builder $query
44
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
45
     * @param string $guard
46
     *
47
     * @return \Illuminate\Database\Eloquent\Builder
48
     */
49 View Code Duplication
    public function scopeRole(Builder $query, $roles, $guard = null): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
50
    {
51
        if ($roles instanceof Collection) {
52
            $roles = $roles->all();
53
        }
54
55
        if (!is_array($roles)) {
56
            $roles = [$roles];
57
        }
58
59
        $roles = $this->getArgumentRoles($roles, $guard);
60
61
        return $query->whereHas('roles', function (Builder $subQuery) use ($roles) {
62
            $subQuery->whereIn(config('permission.table_names.roles').'.id', \array_column($roles, 'id'));
63
        });
64
    }
65
66
    /**
67
     * Scope the model query without certain roles.
68
     *
69
     * @param \Illuminate\Database\Eloquent\Builder $query
70
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
71
     * @param string $guard
72
     *
73
     * @return \Illuminate\Database\Eloquent\Builder
74
     */
75 View Code Duplication
    public function scopeWithoutRole(Builder $query, $roles, $guard = null): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
    {
77
        if ($roles instanceof Collection) {
78
            $roles = $roles->all();
79
        }
80
81
        if (!is_array($roles)) {
82
            $roles = [$roles];
83
        }
84
85
        $roles = $this->getArgumentRoles($roles, $guard);
86
87
        return $query->whereDoesntHave('roles', function (Builder $subQuery) use ($roles) {
88
            $subQuery->whereIn(config('permission.table_names.roles').'.id', \array_column($roles, 'id'));
89
        });
90
    }
91
92
    /**
93
     * Assign the given role to the model.
94
     *
95
     * @param array|string|\Spatie\Permission\Contracts\Role ...$roles
96
     *
97
     * @return $this
98
     */
99 View Code Duplication
    public function assignRole(...$roles)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
100
    {
101
        $roles = collect($roles)
102
            ->flatten()
103
            ->map(function ($role) {
104
                if (empty($role)) {
105
                    return false;
106
                }
107
108
                return $this->getStoredRole($role);
109
            })
110
            ->filter(function ($role) {
111
                return $role instanceof Role;
112
            })
113
            ->each(function ($role) {
114
                $this->ensureModelSharesGuard($role);
115
            })
116
            ->map->id
117
            ->all();
118
119
        $model = $this->getModel();
0 ignored issues
show
Bug introduced by
It seems like getModel() 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...
120
121
        if ($model->exists) {
122
            $this->roles()->sync($roles, false);
123
            $model->load('roles');
124
        } else {
125
            $class = \get_class($model);
126
127
            $class::saved(
128
                function ($object) use ($roles, $model) {
129
                    static $modelLastFiredOn;
130
                    if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) {
131
                        return;
132
                    }
133
                    $object->roles()->sync($roles, false);
134
                    $object->load('roles');
135
                    $modelLastFiredOn = $object;
136
                });
137
        }
138
139
        $this->forgetCachedPermissions();
140
141
        return $this;
142
    }
143
144
    /**
145
     * Revoke the given role from the model.
146
     *
147
     * @param string|\Spatie\Permission\Contracts\Role $role
148
     */
149
    public function removeRole($role)
150
    {
151
        $this->roles()->detach($this->getStoredRole($role));
152
153
        $this->load('roles');
0 ignored issues
show
Bug introduced by
It seems like load() 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...
154
155
        $this->forgetCachedPermissions();
156
157
        return $this;
158
    }
159
160
    /**
161
     * Remove all current roles and set the given ones.
162
     *
163
     * @param  array|\Spatie\Permission\Contracts\Role|string  ...$roles
164
     *
165
     * @return $this
166
     */
167
    public function syncRoles(...$roles)
168
    {
169
        $this->roles()->detach();
170
171
        return $this->assignRole($roles);
172
    }
173
174
    /**
175
     * Determine if the model has (one of) the given role(s).
176
     *
177
     * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
178
     * @param string|null $guard
179
     * @return bool
180
     */
181
    public function hasRole($roles, string $guard = null): bool
182
    {
183 View Code Duplication
        if (is_string($roles) && false !== strpos($roles, '|')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
            $roles = $this->convertPipeToArray($roles);
185
        }
186
187 View Code Duplication
        if (is_string($roles)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
            return $guard
189
                ? $this->roles->where('guard_name', $guard)->contains('name', $roles)
0 ignored issues
show
Bug introduced by
The property roles 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...
190
                : $this->roles->contains('name', $roles);
191
        }
192
193 View Code Duplication
        if (is_int($roles)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
            return $guard
195
                ? $this->roles->where('guard_name', $guard)->contains('id', $roles)
196
                : $this->roles->contains('id', $roles);
197
        }
198
199
        if ($roles instanceof Role) {
200
            return $this->roles->contains('id', $roles->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Spatie\Permission\Contracts\Role suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
201
        }
202
203
        if (is_array($roles)) {
204
            foreach ($roles as $role) {
205
                if ($this->hasRole($role, $guard)) {
206
                    return true;
207
                }
208
            }
209
210
            return false;
211
        }
212
213
        return $roles->intersect($guard ? $this->roles->where('guard_name', $guard) : $this->roles)->isNotEmpty();
214
    }
215
216
    /**
217
     * Determine if the model has any of the given role(s).
218
     *
219
     * Alias to hasRole() but without Guard controls
220
     *
221
     * @param string|int|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
222
     *
223
     * @return bool
224
     */
225
    public function hasAnyRole(...$roles): bool
226
    {
227
        return $this->hasRole($roles);
228
    }
229
230
    /**
231
     * Determine if the model has all of the given role(s).
232
     *
233
     * @param  string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection  $roles
234
     * @param  string|null  $guard
235
     * @return bool
236
     */
237
    public function hasAllRoles($roles, string $guard = null): bool
238
    {
239 View Code Duplication
        if (is_string($roles) && false !== strpos($roles, '|')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
240
            $roles = $this->convertPipeToArray($roles);
241
        }
242
243 View Code Duplication
        if (is_string($roles)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
            return $guard
245
                ? $this->roles->where('guard_name', $guard)->contains('name', $roles)
246
                : $this->roles->contains('name', $roles);
247
        }
248
249
        if ($roles instanceof Role) {
250
            return $this->roles->contains('id', $roles->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Spatie\Permission\Contracts\Role suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
251
        }
252
253
        $roles = collect()->make($roles)->map(function ($role) {
254
            return $role instanceof Role ? $role->name : $role;
0 ignored issues
show
Bug introduced by
Accessing name on the interface Spatie\Permission\Contracts\Role suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
255
        });
256
257
        return $roles->intersect(
258
            $guard
259
                ? $this->roles->where('guard_name', $guard)->pluck('name')
260
                : $this->getRoleNames()) == $roles;
261
    }
262
263
    /**
264
     * Return all permissions directly coupled to the model.
265
     */
266
    public function getDirectPermissions(): Collection
267
    {
268
        return $this->permissions;
0 ignored issues
show
Bug introduced by
The property permissions 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...
269
    }
270
271
    public function getRoleNames(): Collection
272
    {
273
        return $this->roles->pluck('name');
274
    }
275
276
    protected function getStoredRole($role): Role
277
    {
278
        $roleClass = $this->getRoleClass();
279
280
        if (is_numeric($role)) {
281
            return $roleClass->findById($role, $this->getDefaultGuardName());
282
        }
283
284
        if (is_string($role)) {
285
            return $roleClass->findByName($role, $this->getDefaultGuardName());
286
        }
287
288
        return $role;
289
    }
290
291
    protected function convertPipeToArray(string $pipeString)
292
    {
293
        $pipeString = trim($pipeString);
294
295
        if (strlen($pipeString) <= 2) {
296
            return $pipeString;
297
        }
298
299
        $quoteCharacter = substr($pipeString, 0, 1);
300
        $endCharacter = substr($quoteCharacter, -1, 1);
301
302
        if ($quoteCharacter !== $endCharacter) {
303
            return explode('|', $pipeString);
304
        }
305
306
        if (! in_array($quoteCharacter, ["'", '"'])) {
307
            return explode('|', $pipeString);
308
        }
309
310
        return explode('|', trim($pipeString, $quoteCharacter));
311
    }
312
313
    protected function getArgumentRoles($roles, $guard = null)
314
    {
315
        return array_map(function ($role) use ($guard) {
316
            if ($role instanceof Role) {
317
                return $role;
318
            }
319
320
            $method = is_numeric($role) ? 'findById' : 'findByName';
321
            $guard = $guard ?: $this->getDefaultGuardName();
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $guard, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
322
323
            return $this->getRoleClass()->{$method}($role, $guard);
324
        }, $roles);
325
    }
326
}
327