Completed
Push — master ( 5a3129...f38d3d )
by Freek
02:18
created

HasRoles::getPermissionsViaRoles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
                return true;
215
            }
216
        }
217
218
        return false;
219
    }
220
221
    /**
222
     * @deprecated deprecated since version 1.0.1, use hasPermissionTo instead
223
     *
224
     * Determine if the user may perform the given permission.
225
     *
226
     * @param Permission $permission
227
     *
228
     * @return bool
229
     */
230
    public function hasPermission($permission)
231
    {
232
        return $this->hasPermissionTo($permission);
233
    }
234
235
    /**
236
     * Determine if the user has, via roles, the given permission.
237
     *
238
     * @param Permission $permission
239
     *
240
     * @return bool
241
     */
242
    protected function hasPermissionViaRole(Permission $permission)
243
    {
244
        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...
245
    }
246
247
    /**
248
     * Determine if the user has the given permission.
249
     *
250
     * @param string|Permission $permission
251
     *
252
     * @return bool
253
     */
254
    public function hasDirectPermission($permission)
255
    {
256
        if (is_string($permission)) {
257
            $permission = app(Permission::class)->findByName($permission);
258
259
            if (! $permission) {
260
                return false;
261
            }
262
        }
263
264
        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...
265
    }
266
267
    /**
268
     * @param $role
269
     *
270
     * @return Role
271
     */
272
    protected function getStoredRole($role)
273
    {
274
        if (is_string($role)) {
275
            return app(Role::class)->findByName($role);
276
        }
277
278
        return $role;
279
    }
280
281
    /**
282
     * Return all  permissions the directory coupled to the user.
283
     *
284
     * @return \Illuminate\Support\Collection
285
     */
286
    public function getDirectPermissions()
287
    {
288
        return $this->permissions;
289
    }
290
291
    /**
292
     * Return all the permissions the user has via roles.
293
     *
294
     * @return \Illuminate\Support\Collection
295
     */
296
    public function getPermissionsViaRoles()
297
    {
298
        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...
299
            ->roles->flatMap(function ($role) {
300
                return $role->permissions;
301
            })->sort()->values();
302
    }
303
304
    /**
305
     * Return all the permissions the user has, both directly and via roles.
306
     *
307
     * @return \Illuminate\Support\Collection
308
     */
309
    public function getAllPermissions()
310
    {
311
        return $this->permissions->merge($this->getPermissionsViaRoles())->sort()->values();
312
    }
313
}
314