Completed
Push — master ( c3e39f...37ed4c )
by Freek
03:52
created

HasRoles::hasPermission()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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->morphedByMany(
0 ignored issues
show
Bug introduced by
It seems like morphedByMany() 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
            'role_id',
25
            'model_id'
26
        );
27
    }
28
29
    /**
30
     * A model may have multiple direct permissions.
31
     */
32
    public function permissions(): MorphToMany
33
    {
34
        return $this->morphedByMany(
0 ignored issues
show
Bug introduced by
It seems like morphedByMany() 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
            'permission_id',
39
            'model_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
        $roles = array_map(function ($role) {
62
            if ($role instanceof Role) {
63
                return $role;
64
            }
65
66
            $guardName = $this->getGuardName();
67
68
            return app(Role::class)->findByName($role, $guardName);
69
        }, $roles);
70
71
        return $query->whereHas('roles', function ($query) use ($roles) {
72
            $query->where(function ($query) use ($roles) {
73
                foreach ($roles as $role) {
74
                    $query->orWhere(config('permission.table_names.roles').'.id', $role->id);
75
                }
76
            });
77
        });
78
    }
79
80
    /**
81
     * Assign the given role to the model.
82
     *
83
     * @param array|string|\Spatie\Permission\Contracts\Role ...$roles
84
     *
85
     * @return $this
86
     */
87 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...
88
    {
89
        $roles = collect($roles)
90
            ->flatten()
91
            ->map(function ($role) {
92
                return $this->getStoredRole($role);
93
            })
94
            ->each(function ($role) {
95
                $this->ensureGuardIsEqual($role);
96
            })
97
            ->all();
98
99
        $this->roles()->saveMany($roles);
100
101
        $this->forgetCachedPermissions();
102
103
        return $this;
104
    }
105
106
    /**
107
     * Revoke the given role from the model.
108
     *
109
     * @param string|\Spatie\Permission\Contracts\Role $role
110
     */
111
    public function removeRole($role)
112
    {
113
        $this->roles()->detach($this->getStoredRole($role));
114
    }
115
116
    /**
117
     * Remove all current roles and set the given ones.
118
     *
119
     * @param array ...$roles
120
     *
121
     * @return $this
122
     */
123
    public function syncRoles(...$roles)
124
    {
125
        $this->roles()->detach();
126
127
        return $this->assignRole($roles);
128
    }
129
130
    /**
131
     * Determine if the model has (one of) the given role(s).
132
     *
133
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
134
     *
135
     * @return bool
136
     */
137
    public function hasRole($roles): bool
138
    {
139
        if (is_string($roles)) {
140
            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...
141
        }
142
143
        if ($roles instanceof Role) {
144
            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...
145
        }
146
147
        if (is_array($roles)) {
148
            foreach ($roles as $role) {
149
                if ($this->hasRole($role)) {
150
                    return true;
151
                }
152
            }
153
154
            return false;
155
        }
156
157
        return $roles->intersect($this->roles)->isNotEmpty();
158
    }
159
160
    /**
161
     * Determine if the model has any of the given role(s).
162
     *
163
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
164
     *
165
     * @return bool
166
     */
167
    public function hasAnyRole($roles): bool
168
    {
169
        return $this->hasRole($roles);
170
    }
171
172
    /**
173
     * Determine if the model has all of the given role(s).
174
     *
175
     * @param string|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
176
     *
177
     * @return bool
178
     */
179
    public function hasAllRoles($roles): bool
180
    {
181
        if (is_string($roles)) {
182
            return $this->roles->contains('name', $roles);
183
        }
184
185
        if ($roles instanceof Role) {
186
            return $this->roles->contains('id', $roles->id);
187
        }
188
189
        $roles = collect()->make($roles)->map(function ($role) {
190
            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...
191
        });
192
193
        return $roles->intersect($this->roles->pluck('name')) == $roles;
194
    }
195
196
    /**
197
     * Determine if the model may perform the given permission.
198
     *
199
     * @param string|\Spatie\Permission\Contracts\Permission $permission
200
     *
201
     * @return bool
202
     */
203
    public function hasPermissionTo($permission): bool
204
    {
205
        if (is_string($permission)) {
206
            $permission = app(Permission::class)->findByName($permission, $this->getGuardName());
207
        }
208
209
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
210
    }
211
212
    /**
213
     * Determine if the model has any of the given permissions.
214
     *
215
     * @param array ...$permissions
216
     *
217
     * @return bool
218
     */
219
    public function hasAnyPermission(...$permissions): bool
220
    {
221
        foreach ($permissions as $permission) {
222
            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...
223
                return true;
224
            }
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * Determine if the model has, via roles, the given permission.
232
     *
233
     * @param \Spatie\Permission\Contracts\Permission $permission
234
     *
235
     * @return bool
236
     */
237
    protected function hasPermissionViaRole(Permission $permission): bool
238
    {
239
        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...
240
    }
241
242
    /**
243
     * Determine if the model has the given permission.
244
     *
245
     * @param string|\Spatie\Permission\Contracts\Permission $permission
246
     *
247
     * @return bool
248
     */
249
    public function hasDirectPermission($permission): bool
250
    {
251
        if (is_string($permission)) {
252
            $permission = app(Permission::class)->findByName($permission, $this->getGuardName());
253
254
            if (! $permission) {
255
                return false;
256
            }
257
        }
258
259
        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...
260
    }
261
262
    /**
263
     * Return all permissions the directory coupled to the model.
264
     */
265
    public function getDirectPermissions(): Collection
266
    {
267
        return $this->permissions;
268
    }
269
270
    /**
271
     * Return all the permissions the model has via roles.
272
     */
273
    public function getPermissionsViaRoles(): Collection
274
    {
275
        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...
276
            ->roles->flatMap(function ($role) {
277
                return $role->permissions;
278
            })->sort()->values();
279
    }
280
281
    /**
282
     * Return all the permissions the model has, both directly and via roles.
283
     */
284
    public function getAllPermissions(): Collection
285
    {
286
        return $this->permissions
287
            ->merge($this->getPermissionsViaRoles())
288
            ->sort()
289
            ->values();
290
    }
291
292
    protected function getStoredRole($role): Role
293
    {
294
        if (is_string($role)) {
295
            return app(Role::class)->findByName($role, $this->getGuardName());
296
        }
297
298
        return $role;
299
    }
300
}
301