Completed
Push — master ( edb43e...425779 )
by Abdelrahman
10:35
created

HasRoles::scopeRole()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 9
nc 2
nop 2
dl 0
loc 17
rs 8.2222
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * NOTICE OF LICENSE
5
 *
6
 * Part of the Rinvex Fort Package.
7
 *
8
 * This source file is subject to The MIT License (MIT)
9
 * that is bundled with this package in the LICENSE file.
10
 *
11
 * Package: Rinvex Fort Package
12
 * License: The MIT License (MIT)
13
 * Link:    https://rinvex.com
14
 */
15
16
declare(strict_types=1);
17
18
namespace Rinvex\Fort\Traits;
19
20
use Rinvex\Fort\Models\Role;
21
use Illuminate\Support\Collection;
22
use Illuminate\Database\Eloquent\Builder;
23
24
trait HasRoles
25
{
26
    use HasAbilities;
27
28
    /**
29
     * Attach the given roles to the model.
30
     *
31
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
32
     *
33
     * @return $this
34
     */
35
    public function assignRoles($roles)
36
    {
37
        $this->setRoles($roles, 'syncWithoutDetaching');
38
39
        return $this;
40
    }
41
42
    /**
43
     * Sync the given roles to the model.
44
     *
45
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
46
     *
47
     * @return $this
48
     */
49
    public function syncRoles($roles)
50
    {
51
        $this->setRoles($roles, 'sync');
52
53
        return $this;
54
    }
55
56
    /**
57
     * Detach the given roles from the model.
58
     *
59
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
60
     *
61
     * @return $this
62
     */
63
    public function removeRoles($roles)
64
    {
65
        $this->setRoles($roles, 'detach');
66
67
        return $this;
68
    }
69
70
    /**
71
     * Set the given role(s) to the model.
72
     *
73
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
74
     * @param string                                                 $process
75
     *
76
     * @return bool
77
     */
78
    protected function setRoles($roles, string $process)
79
    {
80
        // Guess event name
81
        $event = $process === 'syncWithoutDetaching' ? 'attach' : $process;
82
83
        // If the "attaching/syncing/detaching" event returns false we'll cancel this operation and
84
        // return false, indicating that the attaching/syncing/detaching failed. This provides a
85
        // chance for any listeners to cancel save operations if validations fail or whatever.
86
        if ($this->fireModelEvent($event.'ing') === false) {
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
87
            return false;
88
        }
89
90
        // Hydrate Roles
91
        $roles = $this->hydrateRoles($roles)->pluck('id')->toArray();
92
93
        // Set roles
94
        $this->roles()->$process($roles);
0 ignored issues
show
Bug introduced by
It seems like roles() 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...
95
96
        // Fire the roles attached/synced/detached event
97
        $this->fireModelEvent($event.'ed', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
98
99
        return true;
100
    }
101
102
    /**
103
     * Determine if the entity has (one of) the given roles.
104
     *
105
     * @param string|int|array|\Rinvex\Fort\Models\Role|\Illuminate\Support\Collection $roles
106
     *
107
     * @return bool
108
     */
109
    public function hasRole($roles)
110
    {
111
        // Single role slug
112
        if (is_string($roles)) {
113
            return $this->roles->contains('slug', $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...
114
        }
115
116
        // Single role id
117
        if (is_int($roles)) {
118
            return $this->roles->contains('id', $roles);
119
        }
120
121
        // Single role model
122
        if ($roles instanceof Role) {
123
            return $this->roles->contains('slug', $roles->slug);
124
        }
125
126
        // Array of role slugs
127
        if (is_array($roles) && isset($roles[0]) && is_string($roles[0])) {
128
            return ! $this->roles->pluck('slug')->intersect($roles)->isEmpty();
129
        }
130
131
        // Array of role Ids
132
        if (is_array($roles) && isset($roles[0]) && is_int($roles[0])) {
133
            return ! $this->roles->pluck('id')->intersect($roles)->isEmpty();
134
        }
135
136
        // Collection of role models
137
        if ($roles instanceof Collection) {
138
            return ! $roles->intersect($this->roles->pluck('slug'))->isEmpty();
139
        }
140
141
        return false;
142
    }
143
144
    /**
145
     * Alias for `hasRole` method.
146
     *
147
     * @param string|int|array|\Rinvex\Fort\Models\Role|\Illuminate\Support\Collection $roles
148
     *
149
     * @return bool
150
     */
151
    public function hasAnyRole($roles)
152
    {
153
        return $this->hasRole($roles);
154
    }
155
156
    /**
157
     * Determine if the given entity has all of the given roles.
158
     *
159
     * @param string|int|array|\Rinvex\Fort\Models\Role|\Illuminate\Support\Collection $roles
160
     *
161
     * @return bool
162
     */
163
    public function hasAllRoles($roles)
164
    {
165
        // Single role slug
166
        if (is_string($roles)) {
167
            return $this->roles->contains('slug', $roles);
168
        }
169
170
        // Single role id
171
        if (is_int($roles)) {
172
            return $this->roles->contains('id', $roles);
173
        }
174
175
        // Single role model
176
        if ($roles instanceof Role) {
177
            return $this->roles->contains('slug', $roles->slug);
178
        }
179
180
        // Array of role slugs
181
        if (is_array($roles) && isset($roles[0]) && is_string($roles[0])) {
182
            return $this->roles->pluck('slug')->count() === count($roles)
183
                   && $this->roles->pluck('slug')->diff($roles)->isEmpty();
184
        }
185
186
        // Array of role ids
187
        if (is_array($roles) && isset($roles[0]) && is_int($roles[0])) {
188
            return $this->roles->pluck('id')->count() === count($roles)
189
                   && $this->roles->pluck('id')->diff($roles)->isEmpty();
190
        }
191
192
        // Collection of role models
193
        if ($roles instanceof Collection) {
194
            return $this->roles->count() === $roles->count() && $this->roles->diff($roles)->isEmpty();
195
        }
196
197
        return false;
198
    }
199
200
    /**
201
     * Scope the user query to certain roles only.
202
     *
203
     * @param \Illuminate\Database\Eloquent\Builder                                    $query
204
     * @param string|int|array|\Rinvex\Fort\Models\Role|\Illuminate\Support\Collection $roles
205
     *
206
     * @return \Illuminate\Database\Eloquent\Builder
207
     */
208
    public function scopeRole($query, $roles)
209
    {
210
        if (is_string($roles) || is_int($roles) || $roles instanceof Role) {
211
            $roles = [$roles];
212
        }
213
214
        return $query->whereHas('roles', function (Builder $query) use ($roles) {
215
            $query->where(function (Builder $query) use ($roles) {
216
                foreach ($roles as $role) {
217
                    $column = is_string($role) ? 'slug' : 'id';
218
                    $value = $role instanceof Role ? $role->id : $role;
219
220
                    $query->orWhere(config('rinvex.fort.tables.roles').'.'.$column, $value);
221
                }
222
            });
223
        });
224
    }
225
226
    /**
227
     * Hydrate roles.
228
     *
229
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
230
     *
231
     * @return \Illuminate\Support\Collection
232
     */
233
    protected function hydrateRoles($roles)
234
    {
235
        $isRolesStringBased = $this->isRolesStringBased($roles);
236
        $isRolesIntBased = $this->isRolesIntBased($roles);
237
        $field = $isRolesStringBased ? 'slug' : 'id';
238
239
        return $isRolesStringBased || $isRolesIntBased ? Role::whereIn($field, (array) $roles)->get() : collect($roles);
240
    }
241
242
    /**
243
     * Determine if the given role(ies) are string based.
244
     *
245
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
246
     *
247
     * @return bool
248
     */
249
    protected function isRolesStringBased($roles)
250
    {
251
        return is_string($roles) || (is_array($roles) && isset($roles[0]) && is_string($roles[0]));
252
    }
253
254
    /**
255
     * Determine if the given role(ies) are integer based.
256
     *
257
     * @param int|string|array|\ArrayAccess|\Rinvex\Fort\Models\Role $roles
258
     *
259
     * @return bool
260
     */
261
    protected function isRolesIntBased($roles)
262
    {
263
        return is_int($roles) || (is_array($roles) && isset($roles[0]) && is_int($roles[0]));
264
    }
265
}
266