HasRoleAndPermission   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 418
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 89
dl 0
loc 418
rs 3.36
c 0
b 0
f 0
wmc 63

29 Methods

Rating   Name   Duplication   Size   Complexity  
A pretend() 0 3 1
A hasOneRole() 0 9 3
A syncPermissions() 0 5 1
A checkRole() 0 4 2
A attachRole() 0 7 2
A hasAllRoles() 0 9 3
A getPermissions() 0 3 2
A attachPermission() 0 7 2
A roles() 0 3 1
A allowed() 0 11 4
A hasRole() 0 11 3
A userPermissions() 0 3 1
A syncRoles() 0 5 1
A getArrayFrom() 0 3 2
A __call() 0 3 1
A detachPermission() 0 5 1
A hasAllPermissions() 0 9 3
A hasPermission() 0 11 3
A detachAllRoles() 0 5 1
A detachRole() 0 5 1
A callMagic() 0 11 6
A hasOnePermission() 0 9 3
A isAllowed() 0 11 6
A getRoles() 0 3 2
A detachAllPermissions() 0 5 1
A level() 0 3 2
A isPretendEnabled() 0 3 1
A rolePermissions() 0 15 2
A checkPermission() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like HasRoleAndPermission often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HasRoleAndPermission, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Famdirksen\Roles\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Collection;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
9
use Illuminate\Support\Str;
10
use InvalidArgumentException;
11
use Famdirksen\Roles\Models\Permission;
12
use Famdirksen\Roles\Models\Role;
13
14
trait HasRoleAndPermission
15
{
16
    /**
17
     * Property for caching roles.
18
     *
19
     * @var Collection|null
20
     */
21
    protected $roles;
22
23
    /**
24
     * Property for caching permissions.
25
     *
26
     * @var Collection|null
27
     */
28
    protected $permissions;
29
30
    /**
31
     * User belongs to many roles.
32
     *
33
     * @return BelongsToMany
34
     */
35
    public function roles()
36
    {
37
        return $this->belongsToMany(config('roles.models.role'))->withTimestamps();
0 ignored issues
show
Bug introduced by
The method belongsToMany() does not exist on Famdirksen\Roles\Traits\HasRoleAndPermission. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

37
        return $this->/** @scrutinizer ignore-call */ belongsToMany(config('roles.models.role'))->withTimestamps();
Loading history...
38
    }
39
40
    /**
41
     * Get all roles as collection.
42
     *
43
     * @return Collection
44
     */
45
    public function getRoles()
46
    {
47
        return (!$this->roles) ? $this->roles = $this->roles()->get() : $this->roles;
48
    }
49
50
    /**
51
     * Check if the user has a role or roles.
52
     *
53
     * @param int|string|array $role
54
     * @param bool $all
55
     * @return bool
56
     */
57
    public function hasRole($role, $all = false)
58
    {
59
        if ($this->isPretendEnabled()) {
60
            return $this->pretend('hasRole');
61
        }
62
63
        if (!$all) {
64
            return $this->hasOneRole($role);
65
        }
66
67
        return $this->hasAllRoles($role);
68
    }
69
70
    /**
71
     * Check if the user has at least one of the given roles.
72
     *
73
     * @param int|string|array $role
74
     * @return bool
75
     */
76
    public function hasOneRole($role)
77
    {
78
        foreach ($this->getArrayFrom($role) as $role) {
0 ignored issues
show
introduced by
$role is overwriting one of the parameters of this function.
Loading history...
79
            if ($this->checkRole($role)) {
80
                return true;
81
            }
82
        }
83
84
        return false;
85
    }
86
87
    /**
88
     * Check if the user has all roles.
89
     *
90
     * @param int|string|array $role
91
     * @return bool
92
     */
93
    public function hasAllRoles($role)
94
    {
95
        foreach ($this->getArrayFrom($role) as $role) {
0 ignored issues
show
introduced by
$role is overwriting one of the parameters of this function.
Loading history...
96
            if (!$this->checkRole($role)) {
97
                return false;
98
            }
99
        }
100
101
        return true;
102
    }
103
104
    /**
105
     * Check if the user has role.
106
     *
107
     * @param int|string $role
108
     * @return bool
109
     */
110
    public function checkRole($role)
111
    {
112
        return $this->getRoles()->contains(function ($value) use ($role) {
113
            return $role == $value->id || Str::is($role, $value->slug);
114
        });
115
    }
116
117
    /**
118
     * Attach role to a user.
119
     *
120
     * @param int|Role $role
121
     * @return null|bool
122
     */
123
    public function attachRole($role)
124
    {
125
        if ($this->getRoles()->contains($role)) {
126
            return true;
127
        }
128
        $this->roles = null;
129
        return $this->roles()->attach($role);
130
    }
131
132
    /**
133
     * Detach role from a user.
134
     *
135
     * @param int|Role $role
136
     * @return int
137
     */
138
    public function detachRole($role)
139
    {
140
        $this->roles = null;
141
142
        return $this->roles()->detach($role);
143
    }
144
145
    /**
146
     * Detach all roles from a user.
147
     *
148
     * @return int
149
     */
150
    public function detachAllRoles()
151
    {
152
        $this->roles = null;
153
154
        return $this->roles()->detach();
155
    }
156
157
    /**
158
     * Sync roles for a user.
159
     *
160
     * @param array|\Famdirksen\Roles\Models\Role[]|\Illuminate\Database\Eloquent\Collection $roles
161
     * @return array
162
     */
163
    public function syncRoles($roles)
164
    {
165
        $this->roles = null;
166
167
        return $this->roles()->sync($roles);
168
    }
169
170
    /**
171
     * Get role level of a user.
172
     *
173
     * @return int
174
     */
175
    public function level()
176
    {
177
        return ($role = $this->getRoles()->sortByDesc('level')->first()) ? $role->level : 0;
178
    }
179
180
    /**
181
     * Get all permissions from roles.
182
     *
183
     * @return Builder
184
     */
185
    public function rolePermissions()
186
    {
187
        $permissionModel = app(config('roles.models.permission'));
188
189
        if (!$permissionModel instanceof Model) {
190
            throw new InvalidArgumentException('[roles.models.permission] must be an instance of \Illuminate\Database\Eloquent\Model');
191
        }
192
193
        return $permissionModel
0 ignored issues
show
Bug Best Practice introduced by
The expression return $permissionModel:...sion_role.updated_at')) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
194
            ::select(['permissions.*', 'permission_role.created_at as pivot_created_at', 'permission_role.updated_at as pivot_updated_at'])
195
            ->join('permission_role', 'permission_role.permission_id', '=', 'permissions.id')
196
            ->join('roles', 'roles.id', '=', 'permission_role.role_id')
197
            ->whereIn('roles.id', $this->getRoles()->pluck('id')->toArray())
198
            ->orWhere('roles.level', '<', $this->level())
199
            ->groupBy(['permissions.id', 'permissions.name', 'permissions.slug', 'permissions.description', 'permissions.model', 'permissions.created_at', 'permissions.updated_at', 'permission_role.created_at', 'permission_role.updated_at']);
200
    }
201
202
    /**
203
     * User belongs to many permissions.
204
     *
205
     * @return BelongsToMany
206
     */
207
    public function userPermissions()
208
    {
209
        return $this->belongsToMany(config('roles.models.permission'))->withTimestamps();
210
    }
211
212
    /**
213
     * Get all permissions as collection.
214
     *
215
     * @return Collection
216
     */
217
    public function getPermissions()
218
    {
219
        return (!$this->permissions) ? $this->permissions = $this->rolePermissions()->get()->merge($this->userPermissions()->get()) : $this->permissions;
220
    }
221
222
    /**
223
     * Check if the user has a permission or permissions.
224
     *
225
     * @param int|string|array $permission
226
     * @param bool $all
227
     * @return bool
228
     */
229
    public function hasPermission($permission, $all = false)
230
    {
231
        if ($this->isPretendEnabled()) {
232
            return $this->pretend('hasPermission');
233
        }
234
235
        if (!$all) {
236
            return $this->hasOnePermission($permission);
237
        }
238
239
        return $this->hasAllPermissions($permission);
240
    }
241
242
    /**
243
     * Check if the user has at least one of the given permissions.
244
     *
245
     * @param int|string|array $permission
246
     * @return bool
247
     */
248
    public function hasOnePermission($permission)
249
    {
250
        foreach ($this->getArrayFrom($permission) as $permission) {
0 ignored issues
show
introduced by
$permission is overwriting one of the parameters of this function.
Loading history...
251
            if ($this->checkPermission($permission)) {
252
                return true;
253
            }
254
        }
255
256
        return false;
257
    }
258
259
    /**
260
     * Check if the user has all permissions.
261
     *
262
     * @param int|string|array $permission
263
     * @return bool
264
     */
265
    public function hasAllPermissions($permission)
266
    {
267
        foreach ($this->getArrayFrom($permission) as $permission) {
0 ignored issues
show
introduced by
$permission is overwriting one of the parameters of this function.
Loading history...
268
            if (!$this->checkPermission($permission)) {
269
                return false;
270
            }
271
        }
272
273
        return true;
274
    }
275
276
    /**
277
     * Check if the user has a permission.
278
     *
279
     * @param int|string $permission
280
     * @return bool
281
     */
282
    public function checkPermission($permission)
283
    {
284
        return $this->getPermissions()->contains(function ($value) use ($permission) {
285
            return $permission == $value->id || Str::is($permission, $value->slug);
286
        });
287
    }
288
289
    /**
290
     * Check if the user is allowed to manipulate with entity.
291
     *
292
     * @param string $providedPermission
293
     * @param Model $entity
294
     * @param bool $owner
295
     * @param string $ownerColumn
296
     * @return bool
297
     */
298
    public function allowed($providedPermission, Model $entity, $owner = true, $ownerColumn = 'user_id')
299
    {
300
        if ($this->isPretendEnabled()) {
301
            return $this->pretend('allowed');
302
        }
303
304
        if ($owner === true && $entity->{$ownerColumn} == $this->id) {
305
            return true;
306
        }
307
308
        return $this->isAllowed($providedPermission, $entity);
309
    }
310
311
    /**
312
     * Check if the user is allowed to manipulate with provided entity.
313
     *
314
     * @param string $providedPermission
315
     * @param Model $entity
316
     * @return bool
317
     */
318
    protected function isAllowed($providedPermission, Model $entity)
319
    {
320
        foreach ($this->getPermissions() as $permission) {
321
            if ($permission->model != '' && get_class($entity) == $permission->model
322
                && ($permission->id == $providedPermission || $permission->slug === $providedPermission)
323
            ) {
324
                return true;
325
            }
326
        }
327
328
        return false;
329
    }
330
331
    /**
332
     * Attach permission to a user.
333
     *
334
     * @param int|Permission $permission
335
     * @return null|bool
336
     */
337
    public function attachPermission($permission)
338
    {
339
        if ($this->getPermissions()->contains($permission)) {
340
            return true;
341
        }
342
        $this->permissions = null;
343
        return $this->userPermissions()->attach($permission);
344
    }
345
346
    /**
347
     * Detach permission from a user.
348
     *
349
     * @param int|Permission $permission
350
     * @return int
351
     */
352
    public function detachPermission($permission)
353
    {
354
        $this->permissions = null;
355
356
        return $this->userPermissions()->detach($permission);
357
    }
358
359
    /**
360
     * Detach all permissions from a user.
361
     *
362
     * @return int
363
     */
364
    public function detachAllPermissions()
365
    {
366
        $this->permissions = null;
367
368
        return $this->userPermissions()->detach();
369
    }
370
371
    /**
372
     * Sync permissions for a user.
373
     *
374
     * @param array|\Famdirksen\Roles\Models\Permission[]|\Illuminate\Database\Eloquent\Collection $permissions
375
     * @return array
376
     */
377
    public function syncPermissions($permissions)
378
    {
379
        $this->permissions = null;
380
381
        return $this->userPermissions()->sync($permissions);
382
    }
383
384
    /**
385
     * Check if pretend option is enabled.
386
     *
387
     * @return bool
388
     */
389
    private function isPretendEnabled()
390
    {
391
        return (bool) config('roles.pretend.enabled');
392
    }
393
394
    /**
395
     * Allows to pretend or simulate package behavior.
396
     *
397
     * @param string $option
398
     * @return bool
399
     */
400
    private function pretend($option)
401
    {
402
        return (bool) config('roles.pretend.options.' . $option);
403
    }
404
405
    /**
406
     * Get an array from argument.
407
     *
408
     * @param int|string|array $argument
409
     * @return array
410
     */
411
    private function getArrayFrom($argument)
412
    {
413
        return (!is_array($argument)) ? preg_split('/ ?[,|] ?/', $argument) : $argument;
414
    }
415
416
    public function callMagic($method, $parameters)
417
    {
418
        if (starts_with($method, 'is')) {
0 ignored issues
show
Deprecated Code introduced by
The function starts_with() has been deprecated: Str::startsWith() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

418
        if (/** @scrutinizer ignore-deprecated */ starts_with($method, 'is')) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
419
            return $this->hasRole(snake_case(substr($method, 2), config('roles.separator')));
0 ignored issues
show
Deprecated Code introduced by
The function snake_case() has been deprecated: Str::snake() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

419
            return $this->hasRole(/** @scrutinizer ignore-deprecated */ snake_case(substr($method, 2), config('roles.separator')));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
420
        } elseif (starts_with($method, 'can')) {
0 ignored issues
show
Deprecated Code introduced by
The function starts_with() has been deprecated: Str::startsWith() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

420
        } elseif (/** @scrutinizer ignore-deprecated */ starts_with($method, 'can')) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
421
            return $this->hasPermission(snake_case(substr($method, 3), config('roles.separator')));
0 ignored issues
show
Deprecated Code introduced by
The function snake_case() has been deprecated: Str::snake() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

421
            return $this->hasPermission(/** @scrutinizer ignore-deprecated */ snake_case(substr($method, 3), config('roles.separator')));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
422
        } elseif (starts_with($method, 'allowed')) {
0 ignored issues
show
Deprecated Code introduced by
The function starts_with() has been deprecated: Str::startsWith() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

422
        } elseif (/** @scrutinizer ignore-deprecated */ starts_with($method, 'allowed')) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
423
            return $this->allowed(snake_case(substr($method, 7), config('roles.separator')), $parameters[0], (isset($parameters[1])) ? $parameters[1] : true, (isset($parameters[2])) ? $parameters[2] : 'user_id');
0 ignored issues
show
Deprecated Code introduced by
The function snake_case() has been deprecated: Str::snake() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

423
            return $this->allowed(/** @scrutinizer ignore-deprecated */ snake_case(substr($method, 7), config('roles.separator')), $parameters[0], (isset($parameters[1])) ? $parameters[1] : true, (isset($parameters[2])) ? $parameters[2] : 'user_id');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
424
        }
425
426
        return parent::__call($method, $parameters);
427
    }
428
429
    public function __call($method, $parameters)
430
    {
431
        return $this->callMagic($method, $parameters);
432
    }
433
}
434