Completed
Push — master ( 1af3cf...ff825a )
by Arjay
27:57 queued 13:02
created

HasRole::assignRole()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 3
cp 0
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6

1 Method

Rating   Name   Duplication   Size   Complexity  
A HasRole::roles() 0 4 1
1
<?php
2
3
namespace Yajra\Acl\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
8
use Illuminate\Support\Str;
9
use Yajra\Acl\Models\Role;
10
11
/**
12
 * @property \Illuminate\Database\Eloquent\Collection roles
13
 * @method static Builder havingRoles(array $roleIds)
14
 * @method static Builder havingRolesBySlugs(array $slugs)
15
 */
16
trait HasRole
17
{
18
    private $roleClass;
19
20
    /**
21
     * Check if user have access using any of the acl.
22
     *
23
     * @param  array  $acl
24
     * @return boolean
25
     */
26
    public function canAccess(array $acl): bool
27
    {
28
        return $this->canAtLeast($acl) || $this->hasRole($acl);
29
    }
30
31
    /**
32
     * Check if user has at least one of the given permissions
33
     *
34
     * @param  array  $permissions
35
     * @return bool
36
     */
37
    public function canAtLeast(array $permissions): bool
38
    {
39
        $can = false;
40
41
        if (auth()->check()) {
0 ignored issues
show
Bug introduced by
The method check does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
42
            foreach ($this->roles as $role) {
43
                if ($role->canAtLeast($permissions)) {
44
                    $can = true;
45
                }
46
            }
47
        } else {
48
            $guest = $this->findRoleBySlug('guest');
49
50
            if ($guest) {
51
                return $guest->canAtLeast($permissions);
52
            }
53
        }
54
55
        return $can;
56
    }
57
58
    /**
59
     * Find a role by slug.
60
     *
61
     * @param  string  $slug
62
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
63
     */
64
    protected function findRoleBySlug(string $slug): ?Role
65
    {
66
        return $this->getRoleClass()->newQuery()->where('slug', $slug)->first();
67
    }
68
69
    /**
70
     * Get Role class.
71
     *
72
     * @return Role
73
     */
74
    public function getRoleClass(): Role
75
    {
76
        if (!isset($this->roleClass)) {
77
            $this->roleClass = resolve(config('acl.role'));
78
        }
79
80
        return $this->roleClass;
81
    }
82
83
    /**
84
     * Check if user has the given role.
85
     *
86
     * @param  string|array  $role
87
     * @return bool
88
     */
89
    public function hasRole($role): bool
90
    {
91
        if (is_array($role)) {
92
            $roles = $this->getRoleSlugs();
93
94
            $intersection = array_intersect($roles, (array) $role);
95
            $intersectionCount = count($intersection);
96
97
            return $intersectionCount > 0;
98
        }
99
100
        return $this->roles->contains('slug', $role);
101
    }
102
103
    /**
104
     * Get all user roles.
105
     *
106
     * @return array
107
     */
108
    public function getRoleSlugs(): array
109
    {
110
        return $this->roles->pluck('slug')->toArray();
111
    }
112
113
    /**
114
     * Attach a role to user using slug.
115
     *
116
     * @param  string  $slug
117
     */
118
    public function attachRoleBySlug(string $slug)
119
    {
120
        $this->attachRole($this->findRoleBySlug($slug));
121
    }
122
123
    /**
124
     * Attach a role to user.
125
     *
126
     * @param  mixed  $role
127
     */
128
    public function attachRole($role)
129
    {
130
        $this->roles()->attach($role);
131
    }
132
133
    /**
134
     * Model can have many roles.
135
     *
136
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
137
     */
138
    public function roles(): BelongsToMany
139
    {
140
        return $this->belongsToMany(config('acl.role', Role::class))->withTimestamps();
0 ignored issues
show
Documentation Bug introduced by
The method belongsToMany does not exist on object<Yajra\Acl\Traits\HasRole>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
141
    }
142
143
    /**
144
     * Query scope for user having the given roles.
145
     *
146
     * @param  \Illuminate\Database\Eloquent\Builder  $query
147
     * @param  array  $roles
148
     * @return \Illuminate\Database\Eloquent\Builder
149
     */
150
    public function scopeHavingRoles(Builder $query, array $roles): Builder
151
    {
152
        return $query->whereExists(function ($query) use ($roles) {
153
            $query->selectRaw('1')
154
                ->from('role_user')
155
                ->whereRaw('role_user.user_id = users.id')
156
                ->whereIn('role_id', $roles);
157
        });
158
    }
159
160
    /**
161
     * Query scope for user having the given roles by slugs.
162
     *
163
     * @param  \Illuminate\Database\Eloquent\Builder  $query
164
     * @param  array  $slugs
165
     * @return \Illuminate\Database\Eloquent\Builder
166
     */
167
    public function scopeHavingRolesBySlugs(Builder $query, array $slugs): Builder
168
    {
169
        return $query->whereHas('roles', function ($query) use ($slugs) {
170
            $query->whereIn('roles.slug', $slugs);
171
        });
172
    }
173
174
    /**
175
     * Revokes the given role from the user using slug.
176
     *
177
     * @param  string  $slug
178
     * @return int
179
     */
180
    public function revokeRoleBySlug(string $slug): int
181
    {
182
        return $this->roles()->detach(
183
            $this->findRoleBySlug($slug)
184
        );
185
    }
186
187
    /**
188
     * Revokes the given role from the user.
189
     *
190
     * @param  mixed  $role
191
     * @return int
192
     */
193
    public function revokeRole($role = ""): int
194
    {
195
        return $this->roles()->detach($role);
196
    }
197
198
    /**
199
     * Syncs the given role(s) with the user.
200
     *
201
     * @param  array  $roles
202
     * @return array
203
     */
204
    public function syncRoles(array $roles): array
205
    {
206
        return $this->roles()->sync($roles);
207
    }
208
209
    /**
210
     * Revokes all roles from the user.
211
     *
212
     * @return int
213
     */
214
    public function revokeAllRoles(): int
215
    {
216
        return $this->roles()->detach();
217
    }
218
219
    /**
220
     * Get all user role permissions.
221
     *
222
     * @return array
223
     */
224
    public function getPermissions(): array
225
    {
226
        $permissions = [[], []];
227
228
        foreach ($this->roles as $role) {
229
            $permissions[] = $role->getPermissions();
230
        }
231
232
        return call_user_func_array('array_merge', $permissions);
233
    }
234
235
    /**
236
     * Magic __call method to handle dynamic methods.
237
     *
238
     * @param  string  $method
239
     * @param  array  $arguments
240
     * @return mixed
241
     */
242
    public function __call($method, $arguments = [])
243
    {
244
        // Handle isRoleSlug() methods
245
        if (Str::startsWith($method, 'is') and $method !== 'is') {
246
            $role = substr($method, 2);
247
248
            return $this->isRole($role);
249
        }
250
251
        // Handle canDoSomething() methods
252
        if (Str::startsWith($method, 'can') and $method !== 'can') {
253
            $permission = substr($method, 3);
254
255
            return $this->can($permission);
0 ignored issues
show
Documentation Bug introduced by
The method can does not exist on object<Yajra\Acl\Traits\HasRole>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
256
        }
257
258
        return parent::__call($method, $arguments);
259
    }
260
261
    /**
262
     * Checks if the user has the given role.
263
     *
264
     * @param  string  $slug
265
     * @return bool
266
     */
267
    public function isRole(string $slug): bool
268
    {
269
        $slug = Str::lower($slug);
270
271
        foreach ($this->roles as $role) {
272
            if ($role->slug == $slug) {
273
                return true;
274
            }
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * Check if the given entity/model is owned by the user.
282
     *
283
     * @param  \Illuminate\Database\Eloquent\Model  $entity
284
     * @param  string  $relation
285
     * @return bool
286
     */
287
    public function owns(Model $entity, $relation = 'user_id'): bool
288
    {
289
        return $this->getKeyName() === $entity->{$relation};
0 ignored issues
show
Documentation Bug introduced by
The method getKeyName does not exist on object<Yajra\Acl\Traits\HasRole>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
290
    }
291
}
292