Completed
Pull Request — master (#155)
by Rias
01:45
created

HasRoles::scopeRole()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 32
rs 6.7272
cc 7
eloc 16
nc 4
nop 2
1
<?php
2
3
namespace Spatie\Permission\Traits;
4
5
use Spatie\Permission\Contracts\Permission;
6
use Spatie\Permission\Contracts\Role;
7
8
trait HasRoles
9
{
10
    use HasPermissions;
11
    use RefreshesPermissionCache;
12
13
    /**
14
     * A user may have multiple roles.
15
     *
16
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
17
     */
18
    public function roles()
19
    {
20
        return $this->belongsToMany(
0 ignored issues
show
Bug introduced by
It seems like belongsToMany() 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('laravel-permission.models.role'),
22
            config('laravel-permission.table_names.user_has_roles')
23
        );
24
    }
25
26
    /**
27
     * A user may have multiple direct permissions.
28
     *
29
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
30
     */
31
    public function permissions()
32
    {
33
        return $this->belongsToMany(
0 ignored issues
show
Bug introduced by
It seems like belongsToMany() 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...
34
            config('laravel-permission.models.permission'),
35
            config('laravel-permission.table_names.user_has_permissions')
36
        );
37
    }
38
39
    /**
40
     * Scope the user query to certain roles only
41
     *
42
     * @param string|array|Role|\Illuminate\Support\Collection $roles
43
     *
44
     * @return bool
45
     */
46
    public function scopeRole($query, $roles)
47
    {
48
        if (is_string($roles)) {
49
            return $query->whereHas('roles', function ($query) use ($roles) {
50
                $query->where('name', $roles);
51
            });
52
        }
53
54
        if ($roles instanceof Role) {
55
            return $query->whereHas('roles', function ($query) use ($roles) {
56
                $query->where('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...
57
            });
58
        }
59
60
        if (is_array($roles)) {
61
            return $query->whereHas('roles', function ($query) use ($roles) {
62
                $query->where(function($query) use ($roles) {
63
                    foreach ($roles as $role) {
64
                        if (is_string($role)) {
65
                            $query->orWhere('name', $role);
66
                        }
67
68
                        if ($role instanceof Role) {
69
                            $query->orWhere('id', $role->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...
70
                        }
71
                    }
72
                });
73
            });
74
        }
75
76
        return $query;
77
    }
78
79
    /**
80
     * Assign the given role to the user.
81
     *
82
     * @param array|string|\Spatie\Permission\Models\Role ...$roles
83
     *
84
     * @return \Spatie\Permission\Contracts\Role
85
     */
86
    public function assignRole(...$roles)
87
    {
88
        $roles = collect($roles)
89
            ->flatten()
90
            ->map(function ($role) {
91
                return $this->getStoredRole($role);
92
            })
93
            ->all();
94
95
        $this->roles()->saveMany($roles);
96
97
        $this->forgetCachedPermissions();
98
99
        return $this;
100
    }
101
102
    /**
103
     * Revoke the given role from the user.
104
     *
105
     * @param string|Role $role
106
     */
107
    public function removeRole($role)
108
    {
109
        $this->roles()->detach($this->getStoredRole($role));
110
    }
111
112
    /**
113
     * Remove all current roles and set the given ones.
114
     *
115
     * @param array ...$roles
116
     *
117
     * @return $this
118
     */
119
    public function syncRoles(...$roles)
120
    {
121
        $this->roles()->detach();
122
123
        return $this->assignRole($roles);
124
    }
125
126
    /**
127
     * Determine if the user has (one of) the given role(s).
128
     *
129
     * @param string|array|Role|\Illuminate\Support\Collection $roles
130
     *
131
     * @return bool
132
     */
133
    public function hasRole($roles)
134
    {
135
        if (is_string($roles)) {
136
            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...
137
        }
138
139
        if ($roles instanceof Role) {
140
            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...
141
        }
142
143
        if (is_array($roles)) {
144
            foreach ($roles as $role) {
145
                if ($this->hasRole($role)) {
146
                    return true;
147
                }
148
            }
149
150
            return false;
151
        }
152
153
        return (bool) $roles->intersect($this->roles)->count();
154
    }
155
156
    /**
157
     * Determine if the user has any of the given role(s).
158
     *
159
     * @param string|array|Role|\Illuminate\Support\Collection $roles
160
     *
161
     * @return bool
162
     */
163
    public function hasAnyRole($roles)
164
    {
165
        return $this->hasRole($roles);
166
    }
167
168
    /**
169
     * Determine if the user has all of the given role(s).
170
     *
171
     * @param string|Role|\Illuminate\Support\Collection $roles
172
     *
173
     * @return bool
174
     */
175
    public function hasAllRoles($roles)
176
    {
177
        if (is_string($roles)) {
178
            return $this->roles->contains('name', $roles);
179
        }
180
181
        if ($roles instanceof Role) {
182
            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...
183
        }
184
185
        $roles = collect()->make($roles)->map(function ($role) {
186
            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...
187
        });
188
189
        return $roles->intersect($this->roles->pluck('name')) == $roles;
190
    }
191
192
    /**
193
     * Determine if the user may perform the given permission.
194
     *
195
     * @param string|Permission $permission
196
     *
197
     * @return bool
198
     */
199
    public function hasPermissionTo($permission)
200
    {
201
        if (is_string($permission)) {
202
            $permission = app(Permission::class)->findByName($permission);
203
        }
204
205
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
206
    }
207
208
    /**
209
     * @deprecated deprecated since version 1.0.1, use hasPermissionTo instead
210
     *
211
     * Determine if the user may perform the given permission.
212
     *
213
     * @param Permission $permission
214
     *
215
     * @return bool
216
     */
217
    public function hasPermission($permission)
218
    {
219
        return $this->hasPermissionTo($permission);
220
    }
221
222
    /**
223
     * Determine if the user has, via roles, the given permission.
224
     *
225
     * @param Permission $permission
226
     *
227
     * @return bool
228
     */
229
    protected function hasPermissionViaRole(Permission $permission)
230
    {
231
        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...
232
    }
233
234
    /**
235
     * Determine if the user has the given permission.
236
     *
237
     * @param string|Permission $permission
238
     *
239
     * @return bool
240
     */
241
    protected function hasDirectPermission($permission)
242
    {
243
        if (is_string($permission)) {
244
            $permission = app(Permission::class)->findByName($permission);
245
246
            if (!$permission) {
247
                return false;
248
            }
249
        }
250
251
        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...
252
    }
253
254
    /**
255
     * @param $role
256
     *
257
     * @return Role
258
     */
259
    protected function getStoredRole($role)
260
    {
261
        if (is_string($role)) {
262
            return app(Role::class)->findByName($role);
263
        }
264
265
        return $role;
266
    }
267
}
268