Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like HasPermissions 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 HasPermissions, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | trait HasPermissions |
||
17 | { |
||
18 | private $permissionClass; |
||
19 | |||
20 | View Code Duplication | public static function bootHasPermissions() |
|
|
|||
21 | { |
||
22 | static::deleting(function ($model) { |
||
23 | if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) { |
||
24 | return; |
||
25 | } |
||
26 | |||
27 | $model->permissions()->detach(); |
||
28 | }); |
||
29 | } |
||
30 | |||
31 | public function getPermissionClass() |
||
32 | { |
||
33 | if (! isset($this->permissionClass)) { |
||
34 | $this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass(); |
||
35 | } |
||
36 | |||
37 | return $this->permissionClass; |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * A model may have multiple direct permissions. |
||
42 | */ |
||
43 | public function permissions(): BelongsToMany |
||
44 | { |
||
45 | return $this->morphToMany( |
||
46 | config('permission.models.permission'), |
||
47 | 'model', |
||
48 | config('permission.table_names.model_has_permissions'), |
||
49 | config('permission.column_names.model_morph_key'), |
||
50 | 'permission_id' |
||
51 | ); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Scope the model query to certain permissions only. |
||
56 | * |
||
57 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
58 | * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions |
||
59 | * |
||
60 | * @return \Illuminate\Database\Eloquent\Builder |
||
61 | */ |
||
62 | public function scopePermission(Builder $query, $permissions): Builder |
||
63 | { |
||
64 | $permissions = $this->convertToPermissionModels($permissions); |
||
65 | |||
66 | $rolesWithPermissions = array_unique(array_reduce($permissions, function ($result, $permission) { |
||
67 | return array_merge($result, $permission->roles->all()); |
||
68 | }, [])); |
||
69 | |||
70 | return $query->where(function (Builder $query) use ($permissions, $rolesWithPermissions) { |
||
71 | $query->whereHas('permissions', function (Builder $subQuery) use ($permissions) { |
||
72 | $subQuery->whereIn(config('permission.table_names.permissions').'.id', \array_column($permissions, 'id')); |
||
73 | }); |
||
74 | if (count($rolesWithPermissions) > 0) { |
||
75 | $query->orWhereHas('roles', function (Builder $subQuery) use ($rolesWithPermissions) { |
||
76 | $subQuery->whereIn(config('permission.table_names.roles').'.id', \array_column($rolesWithPermissions, 'id')); |
||
77 | }); |
||
78 | } |
||
79 | }); |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions |
||
84 | * |
||
85 | * @return array |
||
86 | */ |
||
87 | protected function convertToPermissionModels($permissions): array |
||
88 | { |
||
89 | if ($permissions instanceof Collection) { |
||
90 | $permissions = $permissions->all(); |
||
91 | } |
||
92 | |||
93 | $permissions = is_array($permissions) ? $permissions : [$permissions]; |
||
94 | |||
95 | return array_map(function ($permission) { |
||
96 | if ($permission instanceof Permission) { |
||
97 | return $permission; |
||
98 | } |
||
99 | |||
100 | return $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName()); |
||
101 | }, $permissions); |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Determine if the model may perform the given permission. |
||
106 | * |
||
107 | * @param string|int|\Spatie\Permission\Contracts\Permission $permission |
||
108 | * @param string|null $guardName |
||
109 | * |
||
110 | * @return bool |
||
111 | * @throws PermissionDoesNotExist |
||
112 | */ |
||
113 | public function hasPermissionTo($permission, $guardName = null): bool |
||
114 | { |
||
115 | if (config('permission.enable_wildcard_permission', false)) { |
||
116 | return $this->hasWildcardPermission($permission, $guardName); |
||
117 | } |
||
118 | |||
119 | $permissionClass = $this->getPermissionClass(); |
||
120 | |||
121 | if (is_string($permission)) { |
||
122 | $permission = $permissionClass->findByName( |
||
123 | $permission, |
||
124 | $guardName ?? $this->getDefaultGuardName() |
||
125 | ); |
||
126 | } |
||
127 | |||
128 | if (is_int($permission)) { |
||
129 | $permission = $permissionClass->findById( |
||
130 | $permission, |
||
131 | $guardName ?? $this->getDefaultGuardName() |
||
132 | ); |
||
133 | } |
||
134 | |||
135 | if (! $permission instanceof Permission) { |
||
136 | throw new PermissionDoesNotExist; |
||
137 | } |
||
138 | |||
139 | return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Validates a wildcard permission against all permissions of a user. |
||
144 | * |
||
145 | * @param string|int|\Spatie\Permission\Contracts\Permission $permission |
||
146 | * @param string|null $guardName |
||
147 | * |
||
148 | * @return bool |
||
149 | */ |
||
150 | protected function hasWildcardPermission($permission, $guardName = null): bool |
||
151 | { |
||
152 | $guardName = $guardName ?? $this->getDefaultGuardName(); |
||
153 | |||
154 | if (is_int($permission)) { |
||
155 | $permission = $this->getPermissionClass()->findById($permission, $guardName); |
||
156 | } |
||
157 | |||
158 | if ($permission instanceof Permission) { |
||
159 | $permission = $permission->name; |
||
160 | } |
||
161 | |||
162 | if (! is_string($permission)) { |
||
163 | throw WildcardPermissionInvalidArgument::create(); |
||
164 | } |
||
165 | |||
166 | foreach ($this->getAllPermissions() as $userPermission) { |
||
167 | if ($guardName !== $userPermission->guard_name) { |
||
168 | continue; |
||
169 | } |
||
170 | |||
171 | $userPermission = new WildcardPermission($userPermission->name); |
||
172 | |||
173 | if ($userPermission->implies($permission)) { |
||
174 | return true; |
||
175 | } |
||
176 | } |
||
177 | |||
178 | return false; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * @deprecated since 2.35.0 |
||
183 | * @alias of hasPermissionTo() |
||
184 | */ |
||
185 | public function hasUncachedPermissionTo($permission, $guardName = null): bool |
||
186 | { |
||
187 | return $this->hasPermissionTo($permission, $guardName); |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * An alias to hasPermissionTo(), but avoids throwing an exception. |
||
192 | * |
||
193 | * @param string|int|\Spatie\Permission\Contracts\Permission $permission |
||
194 | * @param string|null $guardName |
||
195 | * |
||
196 | * @return bool |
||
197 | */ |
||
198 | public function checkPermissionTo($permission, $guardName = null): bool |
||
199 | { |
||
200 | try { |
||
201 | return $this->hasPermissionTo($permission, $guardName); |
||
202 | } catch (PermissionDoesNotExist $e) { |
||
203 | return false; |
||
204 | } |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Determine if the model has any of the given permissions. |
||
209 | * |
||
210 | * @param array ...$permissions |
||
211 | * |
||
212 | * @return bool |
||
213 | * @throws \Exception |
||
214 | */ |
||
215 | View Code Duplication | public function hasAnyPermission(...$permissions): bool |
|
216 | { |
||
217 | $permissions = collect($permissions)->flatten(); |
||
218 | |||
219 | foreach ($permissions as $permission) { |
||
220 | if ($this->checkPermissionTo($permission)) { |
||
221 | return true; |
||
222 | } |
||
223 | } |
||
224 | |||
225 | return false; |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Determine if the model has all of the given permissions. |
||
230 | * |
||
231 | * @param array ...$permissions |
||
232 | * |
||
233 | * @return bool |
||
234 | * @throws \Exception |
||
235 | */ |
||
236 | View Code Duplication | public function hasAllPermissions(...$permissions): bool |
|
237 | { |
||
238 | $permissions = collect($permissions)->flatten(); |
||
239 | |||
240 | foreach ($permissions as $permission) { |
||
241 | if (! $this->hasPermissionTo($permission)) { |
||
242 | return false; |
||
243 | } |
||
244 | } |
||
245 | |||
246 | return true; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Determine if the model has, via roles, the given permission. |
||
251 | * |
||
252 | * @param \Spatie\Permission\Contracts\Permission $permission |
||
253 | * |
||
254 | * @return bool |
||
255 | */ |
||
256 | protected function hasPermissionViaRole(Permission $permission): bool |
||
257 | { |
||
258 | return $this->hasRole($permission->roles); |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Determine if the model has the given permission. |
||
263 | * |
||
264 | * @param string|int|\Spatie\Permission\Contracts\Permission $permission |
||
265 | * |
||
266 | * @return bool |
||
267 | * @throws PermissionDoesNotExist |
||
268 | */ |
||
269 | public function hasDirectPermission($permission): bool |
||
270 | { |
||
271 | $permissionClass = $this->getPermissionClass(); |
||
272 | |||
273 | if (is_string($permission)) { |
||
274 | $permission = $permissionClass->findByName($permission, $this->getDefaultGuardName()); |
||
275 | } |
||
276 | |||
277 | if (is_int($permission)) { |
||
278 | $permission = $permissionClass->findById($permission, $this->getDefaultGuardName()); |
||
279 | } |
||
280 | |||
281 | if (! $permission instanceof Permission) { |
||
282 | throw new PermissionDoesNotExist; |
||
283 | } |
||
284 | |||
285 | return $this->permissions->contains('id', $permission->id); |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Return all the permissions the model has via roles. |
||
290 | */ |
||
291 | public function getPermissionsViaRoles(): Collection |
||
292 | { |
||
293 | return $this->loadMissing('roles', 'roles.permissions') |
||
294 | ->roles->flatMap(function ($role) { |
||
295 | return $role->permissions; |
||
296 | })->sort()->values(); |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Return all the permissions the model has, both directly and via roles. |
||
301 | */ |
||
302 | public function getAllPermissions(): Collection |
||
303 | { |
||
304 | /** @var Collection $permissions */ |
||
305 | $permissions = $this->permissions; |
||
306 | |||
307 | if ($this->roles) { |
||
308 | $permissions = $permissions->merge($this->getPermissionsViaRoles()); |
||
309 | } |
||
310 | |||
311 | return $permissions->sort()->values(); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Grant the given permission(s) to a role. |
||
316 | * |
||
317 | * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions |
||
318 | * |
||
319 | * @return $this |
||
320 | */ |
||
321 | View Code Duplication | public function givePermissionTo(...$permissions) |
|
322 | { |
||
323 | $permissions = collect($permissions) |
||
324 | ->flatten() |
||
325 | ->map(function ($permission) { |
||
326 | if (empty($permission)) { |
||
327 | return false; |
||
328 | } |
||
329 | |||
330 | return $this->getStoredPermission($permission); |
||
331 | }) |
||
332 | ->filter(function ($permission) { |
||
333 | return $permission instanceof Permission; |
||
334 | }) |
||
335 | ->each(function ($permission) { |
||
336 | $this->ensureModelSharesGuard($permission); |
||
337 | }) |
||
338 | ->map->id |
||
339 | ->all(); |
||
340 | |||
341 | $model = $this->getModel(); |
||
342 | |||
343 | if ($model->exists) { |
||
344 | $this->permissions()->sync($permissions, false); |
||
345 | $model->load('permissions'); |
||
346 | } else { |
||
347 | $class = \get_class($model); |
||
348 | |||
349 | $class::saved( |
||
350 | function ($object) use ($permissions, $model) { |
||
351 | static $modelLastFiredOn; |
||
352 | if ($modelLastFiredOn !== null && $modelLastFiredOn === $model) { |
||
353 | return; |
||
354 | } |
||
355 | $object->permissions()->sync($permissions, false); |
||
356 | $object->load('permissions'); |
||
357 | $modelLastFiredOn = $object; |
||
358 | } |
||
359 | ); |
||
360 | } |
||
361 | |||
362 | $this->forgetCachedPermissions(); |
||
363 | |||
364 | return $this; |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * Remove all current permissions and set the given ones. |
||
369 | * |
||
370 | * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions |
||
371 | * |
||
372 | * @return $this |
||
373 | */ |
||
374 | public function syncPermissions(...$permissions) |
||
375 | { |
||
376 | $this->permissions()->detach(); |
||
377 | |||
378 | return $this->givePermissionTo($permissions); |
||
379 | } |
||
380 | |||
381 | /** |
||
382 | * Revoke the given permission. |
||
383 | * |
||
384 | * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission |
||
385 | * |
||
386 | * @return $this |
||
387 | */ |
||
388 | public function revokePermissionTo($permission) |
||
389 | { |
||
390 | $this->permissions()->detach($this->getStoredPermission($permission)); |
||
391 | |||
392 | $this->forgetCachedPermissions(); |
||
393 | |||
394 | $this->load('permissions'); |
||
395 | |||
396 | return $this; |
||
397 | } |
||
398 | |||
399 | public function getPermissionNames(): Collection |
||
400 | { |
||
401 | return $this->permissions->pluck('name'); |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions |
||
406 | * |
||
407 | * @return \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|\Illuminate\Support\Collection |
||
408 | */ |
||
409 | protected function getStoredPermission($permissions) |
||
410 | { |
||
411 | $permissionClass = $this->getPermissionClass(); |
||
412 | |||
413 | if (is_numeric($permissions)) { |
||
414 | return $permissionClass->findById($permissions, $this->getDefaultGuardName()); |
||
415 | } |
||
416 | |||
417 | if (is_string($permissions)) { |
||
418 | return $permissionClass->findByName($permissions, $this->getDefaultGuardName()); |
||
419 | } |
||
420 | |||
421 | if (is_array($permissions)) { |
||
422 | return $permissionClass |
||
423 | ->whereIn('name', $permissions) |
||
424 | ->whereIn('guard_name', $this->getGuardNames()) |
||
425 | ->get(); |
||
426 | } |
||
427 | |||
428 | return $permissions; |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Role $roleOrPermission |
||
433 | * |
||
434 | * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch |
||
435 | */ |
||
436 | protected function ensureModelSharesGuard($roleOrPermission) |
||
442 | |||
443 | protected function getGuardNames(): Collection |
||
447 | |||
448 | protected function getDefaultGuardName(): string |
||
452 | |||
453 | /** |
||
454 | * Forget the cached permissions. |
||
455 | */ |
||
456 | public function forgetCachedPermissions() |
||
457 | { |
||
458 | app(PermissionRegistrar::class)->forgetCachedPermissions(); |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * Check if the model has All of the requested Direct permissions. |
||
463 | * @param array ...$permissions |
||
464 | * @return bool |
||
465 | */ |
||
466 | View Code Duplication | public function hasAllDirectPermissions(...$permissions): bool |
|
467 | { |
||
468 | $permissions = collect($permissions)->flatten(); |
||
469 | |||
470 | foreach ($permissions as $permission) { |
||
471 | if (! $this->hasDirectPermission($permission)) { |
||
472 | return false; |
||
478 | |||
479 | /** |
||
480 | * Check if the model has Any of the requested Direct permissions. |
||
481 | * @param array ...$permissions |
||
482 | * @return bool |
||
483 | */ |
||
484 | View Code Duplication | public function hasAnyDirectPermission(...$permissions): bool |
|
496 | } |
||
497 |
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.