Completed
Push — master ( 0bb0be...fc50e7 )
by Freek
01:50
created

HasRoles::hasRole()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 26
Code Lines 13

Duplication

Lines 3
Ratio 11.54 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 8
eloc 13
nc 12
nop 1
dl 3
loc 26
rs 5.3846
c 1
b 0
f 1
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\Contracts\Permission;
9
use Illuminate\Database\Eloquent\Relations\MorphToMany;
10
11
trait HasRoles
12
{
13
    use HasPermissions;
14
15
    /**
16
     * A model may have multiple roles.
17
     */
18
    public function roles(): MorphToMany
19
    {
20
        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...
21
            config('permission.models.role'),
22
            'model',
23
            config('permission.table_names.model_has_roles'),
24
            'model_id',
25
            'role_id'
26
        );
27
    }
28
29
    /**
30
     * A model may have multiple direct permissions.
31
     */
32
    public function permissions(): MorphToMany
33
    {
34
        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...
35
            config('permission.models.permission'),
36
            'model',
37
            config('permission.table_names.model_has_permissions'),
38
            'model_id',
39
            'permission_id'
40
        );
41
    }
42
43
    /**
44
     * Scope the model query to certain roles only.
45
     *
46
     * @param \Illuminate\Database\Eloquent\Builder $query
47
     * @param string|array|Role|\Illuminate\Support\Collection $roles
48
     *
49
     * @return \Illuminate\Database\Eloquent\Builder
50
     */
51
    public function scopeRole(Builder $query, $roles): Builder
52
    {
53
        if ($roles instanceof Collection) {
54
            $roles = $roles->toArray();
55
        }
56
57
        if (! is_array($roles)) {
58
            $roles = [$roles];
59
        }
60
61 View Code Duplication
        $roles = array_map(function ($role) {
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...
62
            if ($role instanceof Role) {
63
                return $role;
64
            }
65
66
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
67
        }, $roles);
68
69
        return $query->whereHas('roles', function ($query) use ($roles) {
70
            $query->where(function ($query) use ($roles) {
71
                foreach ($roles as $role) {
72
                    $query->orWhere(config('permission.table_names.roles').'.id', $role->id);
73
                }
74
            });
75
        });
76
    }
77
78
    /**
79
     * Assign the given role to the model.
80
     *
81
     * @param array|string|\Spatie\Permission\Contracts\Role ...$roles
82
     *
83
     * @return $this
84
     */
85
    public function assignRole(...$roles)
86
    {
87
        $roles = collect($roles)
88
            ->flatten()
89
            ->map(function ($role) {
90
                return $this->getStoredRole($role);
91
            })
92
            ->each(function ($role) {
93
                $this->ensureModelSharesGuard($role);
94
            })
95
            ->all();
96
97
        $this->roles()->saveMany($roles);
98
99
        $this->forgetCachedPermissions();
100
101
        return $this;
102
    }
103
104
    /**
105
     * Revoke the given role from the model.
106
     *
107
     * @param string|\Spatie\Permission\Contracts\Role $role
108
     */
109
    public function removeRole($role)
110
    {
111
        $this->roles()->detach($this->getStoredRole($role));
112
    }
113
114
    /**
115
     * Remove all current roles and set the given ones.
116
     *
117
     * @param array ...$roles
118
     *
119
     * @return $this
120
     */
121
    public function syncRoles(...$roles)
122
    {
123
        $this->roles()->detach();
124
125
        return $this->assignRole($roles);
126
    }
127
128
    /**
129
     * Determine if the model has (one of) the given role(s).
130
     *
131
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
132
     *
133
     * @return bool
134
     */
135
    public function hasRole($roles): bool
136
    {
137 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...
138
            $roles = $this->convertPipeToArray($roles);
139
        }
140
141
        if (is_string($roles)) {
142
            return $this->roles->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...
143
        }
144
145
        if ($roles instanceof Role) {
146
            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...
147
        }
148
149
        if (is_array($roles)) {
150
            foreach ($roles as $role) {
151
                if ($this->hasRole($role)) {
152
                    return true;
153
                }
154
            }
155
156
            return false;
157
        }
158
159
        return $roles->intersect($this->roles)->isNotEmpty();
160
    }
161
162
    /**
163
     * Determine if the model has any of the given role(s).
164
     *
165
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
166
     *
167
     * @return bool
168
     */
169
    public function hasAnyRole($roles): bool
170
    {
171
        return $this->hasRole($roles);
172
    }
173
174
    /**
175
     * Determine if the model has all of the given role(s).
176
     *
177
     * @param string|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
178
     *
179
     * @return bool
180
     */
181
    public function hasAllRoles($roles): 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
        if (is_string($roles)) {
188
            return $this->roles->contains('name', $roles);
189
        }
190
191
        if ($roles instanceof Role) {
192
            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...
193
        }
194
195
        $roles = collect()->make($roles)->map(function ($role) {
196
            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...
197
        });
198
199
        return $roles->intersect($this->roles->pluck('name')) == $roles;
200
    }
201
202
    /**
203
     * Determine if the model may perform the given permission.
204
     *
205
     * @param string|\Spatie\Permission\Contracts\Permission $permission
206
     * @param string|null $guardName
207
     *
208
     * @return bool
209
     */
210
    public function hasPermissionTo($permission, $guardName = null): bool
211
    {
212 View Code Duplication
        if (is_string($permission)) {
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...
213
            $permission = app(Permission::class)->findByName(
214
                $permission,
215
                $guardName ?? $this->getDefaultGuardName()
216
            );
217
        }
218
219
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
220
    }
221
222
    /**
223
     * Determine if the model has any of the given permissions.
224
     *
225
     * @param array ...$permissions
226
     *
227
     * @return bool
228
     */
229
    public function hasAnyPermission(...$permissions): bool
230
    {
231
        if (is_array($permissions[0])) {
232
            $permissions = $permissions[0];
233
        }
234
235
        foreach ($permissions as $permission) {
236
            if ($this->hasPermissionTo($permission)) {
237
                return true;
238
            }
239
        }
240
241
        return false;
242
    }
243
244
    /**
245
     * Determine if the model has, via roles, the given permission.
246
     *
247
     * @param \Spatie\Permission\Contracts\Permission $permission
248
     *
249
     * @return bool
250
     */
251
    protected function hasPermissionViaRole(Permission $permission): bool
252
    {
253
        return $this->hasRole($permission->roles);
0 ignored issues
show
Bug introduced by
Accessing roles on the interface Spatie\Permission\Contracts\Permission 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...
254
    }
255
256
    /**
257
     * Determine if the model has the given permission.
258
     *
259
     * @param string|\Spatie\Permission\Contracts\Permission $permission
260
     *
261
     * @return bool
262
     */
263
    public function hasDirectPermission($permission): bool
264
    {
265 View Code Duplication
        if (is_string($permission)) {
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...
266
            $permission = app(Permission::class)->findByName($permission, $this->getDefaultGuardName());
267
268
            if (! $permission) {
269
                return false;
270
            }
271
        }
272
273
        return $this->permissions->contains('id', $permission->id);
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...
274
    }
275
276
    /**
277
     * Return all permissions the directory coupled to the model.
278
     */
279
    public function getDirectPermissions(): Collection
280
    {
281
        return $this->permissions;
282
    }
283
284
    /**
285
     * Return all the permissions the model has via roles.
286
     */
287
    public function getPermissionsViaRoles(): Collection
288
    {
289
        return $this->load('roles', 'roles.permissions')
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...
290
            ->roles->flatMap(function ($role) {
291
                return $role->permissions;
292
            })->sort()->values();
293
    }
294
295
    /**
296
     * Return all the permissions the model has, both directly and via roles.
297
     */
298
    public function getAllPermissions(): Collection
299
    {
300
        return $this->permissions
301
            ->merge($this->getPermissionsViaRoles())
302
            ->sort()
303
            ->values();
304
    }
305
306 View Code Duplication
    protected function getStoredRole($role): Role
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...
307
    {
308
        if (is_string($role)) {
309
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
310
        }
311
312
        return $role;
313
    }
314
315
    protected function convertPipeToArray(string $pipeString)
316
    {
317
        $pipeString = trim($pipeString);
318
319
        if (strlen($pipeString) <= 2) {
320
            return $pipeString;
321
        }
322
323
        $quoteCharacter = substr($pipeString, 0, 1);
324
        $endCharacter = substr($quoteCharacter, -1, 1);
325
326
        if ($quoteCharacter !== $endCharacter) {
327
            return explode('|', $pipeString);
328
        }
329
330
        if (! in_array($quoteCharacter, ["'", '"'])) {
331
            return explode('|', $pipeString);
332
        }
333
334
        return explode('|', trim($pipeString, $quoteCharacter));
335
    }
336
}
337