Completed
Pull Request — master (#38)
by Arjay
11:31
created

HasRole::attachRoleBySlug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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\Permission;
10
use Yajra\Acl\Models\Role;
11
12
/**
13
 * @property \Illuminate\Database\Eloquent\Collection roles
14
 * @method static Builder havingRoles(array $roleIds)
15
 * @method static Builder havingRolesBySlugs(array $slugs)
16
 */
17
trait HasRole
18
{
19
    private $roleClass;
20
21
    /**
22
     * Check if user have access using any of the acl.
23
     *
24
     * @param  array  $acl
25
     * @return boolean
26
     */
27
    public function canAccess(array $acl): bool
28
    {
29
        return $this->canAtLeast($acl) || $this->hasRole($acl);
30
    }
31
32
    /**
33
     * Check if user has at least one of the given permissions
34
     *
35
     * @param  array  $permissions
36
     * @return bool
37
     */
38
    public function canAtLeast(array $permissions): bool
39
    {
40
        $can = false;
41
42
        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...
43
            foreach ($this->roles as $role) {
44
                if ($role->canAtLeast($permissions)) {
45
                    $can = true;
46
                }
47
            }
48
        } else {
49
            $guest = $this->findRoleBySlug('guest');
50
51
            if ($guest) {
52
                return $guest->canAtLeast($permissions);
53
            }
54
        }
55
56
        return $can;
57
    }
58
59
    /**
60
     * Get Role class.
61
     *
62
     * @return Role
63
     */
64
    public function getRoleClass(): Role
65
    {
66
        if (!isset($this->roleClass)) {
67
            $this->roleClass = resolve(config('acl.role'));
68
        }
69
70
        return $this->roleClass;
71
    }
72
73
    /**
74
     * Check if user has the given role.
75
     *
76
     * @param  string|array  $role
77
     * @return bool
78
     */
79
    public function hasRole($role): bool
80
    {
81
        if (is_string($role)) {
82
            return $this->roles->contains('slug', $role);
83
        }
84
85
        if (is_array($role)) {
86
            $roles = $this->getRoleSlugs();
87
88
            $intersection = array_intersect($roles, (array) $role);
89
            $intersectionCount = count($intersection);
90
91
            return $intersectionCount > 0;
92
        }
93
94
        return !!$role->intersect($this->roles)->count();
95
    }
96
97
    /**
98
     * Get all user roles.
99
     *
100
     * @return array|null
101
     */
102
    public function getRoleSlugs()
103
    {
104
        if (!is_null($this->roles)) {
105
            return $this->roles->pluck('slug')->toArray();
106
        }
107
108
        return null;
109
    }
110
111
    /**
112
     * Attach a role to user using slug.
113
     *
114
     * @param  string  $slug
115
     */
116
    public function attachRoleBySlug(string $slug)
117
    {
118
        $this->attachRole($this->findRoleBySlug($slug));
119
    }
120
121
    /**
122
     * Attach a role to user.
123
     *
124
     * @param  mixed  $role
125
     */
126
    public function attachRole($role)
127
    {
128
        $this->roles()->attach($role);
129
    }
130
131
    /**
132
     * Model can have many roles.
133
     *
134
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
135
     */
136
    public function roles(): BelongsToMany
137
    {
138
        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...
139
    }
140
141
    /**
142
     * Find a role by slug.
143
     *
144
     * @param  string  $slug
145
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
146
     */
147
    protected function findRoleBySlug(string $slug): ?Role
148
    {
149
        return $this->getRoleClass()->newQuery()->where('slug', $slug)->first();
150
    }
151
152
    /**
153
     * Query scope for user having the given roles.
154
     *
155
     * @param  \Illuminate\Database\Eloquent\Builder  $query
156
     * @param  array  $roles
157
     * @return \Illuminate\Database\Eloquent\Builder
158
     */
159
    public function scopeHavingRoles(Builder $query, array $roles): Builder
160
    {
161
        return $query->whereExists(function ($query) use ($roles) {
162
            $query->selectRaw('1')
163
                ->from('role_user')
164
                ->whereRaw('role_user.user_id = users.id')
165
                ->whereIn('role_id', $roles);
166
        });
167
    }
168
169
    /**
170
     * Query scope for user having the given roles by slugs.
171
     *
172
     * @param  \Illuminate\Database\Eloquent\Builder  $query
173
     * @param  array  $slugs
174
     * @return \Illuminate\Database\Eloquent\Builder
175
     */
176
    public function scopeHavingRolesBySlugs(Builder $query, array $slugs): Builder
177
    {
178
        return $query->whereHas('roles', function ($query) use ($slugs) {
179
            $query->whereIn('roles.slug', $slugs);
180
        });
181
    }
182
183
    /**
184
     * Revokes the given role from the user using slug.
185
     *
186
     * @param  string  $slug
187
     * @return int
188
     */
189
    public function revokeRoleBySlug(string $slug): int
190
    {
191
        return $this->roles()->detach(
192
            $this->findRoleBySlug($slug)
193
        );
194
    }
195
196
    /**
197
     * Revokes the given role from the user.
198
     *
199
     * @param  mixed  $role
200
     * @return int
201
     */
202
    public function revokeRole($role = ""): int
203
    {
204
        return $this->roles()->detach($role);
205
    }
206
207
    /**
208
     * Syncs the given role(s) with the user.
209
     *
210
     * @param  array  $roles
211
     * @return array
212
     */
213
    public function syncRoles(array $roles): array
214
    {
215
        return $this->roles()->sync($roles);
216
    }
217
218
    /**
219
     * Revokes all roles from the user.
220
     *
221
     * @return int
222
     */
223
    public function revokeAllRoles(): int
224
    {
225
        return $this->roles()->detach();
226
    }
227
228
    /**
229
     * Get all user role permissions.
230
     *
231
     * @return array
232
     */
233
    public function getPermissions(): array
234
    {
235
        $permissions = [[], []];
236
237
        foreach ($this->roles as $role) {
238
            $permissions[] = $role->getPermissions();
239
        }
240
241
        return call_user_func_array('array_merge', $permissions);
242
    }
243
244
    /**
245
     * Magic __call method to handle dynamic methods.
246
     *
247
     * @param  string  $method
248
     * @param  array  $arguments
249
     * @return mixed
250
     */
251
    public function __call($method, $arguments = [])
252
    {
253
        // Handle isRoleSlug() methods
254
        if (Str::startsWith($method, 'is') and $method !== 'is') {
255
            $role = substr($method, 2);
256
257
            return $this->isRole($role);
258
        }
259
260
        // Handle canDoSomething() methods
261
        if (Str::startsWith($method, 'can') and $method !== 'can') {
262
            $permission = substr($method, 3);
263
264
            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...
265
        }
266
267
        return parent::__call($method, $arguments);
268
    }
269
270
    /**
271
     * Checks if the user has the given role.
272
     *
273
     * @param  string  $slug
274
     * @return bool
275
     */
276
    public function isRole(string $slug): bool
277
    {
278
        $slug = Str::lower($slug);
279
280
        foreach ($this->roles as $role) {
281
            if ($role->slug == $slug) {
282
                return true;
283
            }
284
        }
285
286
        return false;
287
    }
288
289
    /**
290
     * Check if the given entity/model is owned by the user.
291
     *
292
     * @param  \Illuminate\Database\Eloquent\Model  $entity
293
     * @param  string  $relation
294
     * @return bool
295
     */
296
    public function owns(Model $entity, $relation = 'user_id'): bool
297
    {
298
        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...
299
    }
300
}
301