php
45 lines · 8 steps
Idempotent role assignment in Laravel
A service that attaches department-scoped roles to a user with pivot metadata, without duplicating existing assignments.
Explained by
highlit
1<?php
2
3namespace App\Services;
4
5use App\Models\Role;
6use App\Models\User;
7use Illuminate\Support\Collection;
8use Illuminate\Support\Facades\DB;
9
10class RoleAssignmentService
11{
12 public function assignDepartmentRoles(User $user, array $roleSlugs, int $assignedBy): Collection
13 {
14 $roles = Role::query()
15 ->where('department_id', $user->department_id)
16 ->whereIn('slug', $roleSlugs)
17 ->get();
18
19 if ($roles->isEmpty()) {
20 return collect();
21 }
22
23 $now = now();
24
25 $pivotData = $roles->mapWithKeys(fn (Role $role) => [
26 $role->getKey() => [
27 'assigned_by' => $assignedBy,
28 'assigned_at' => $now,
29 'source' => 'department',
30 ],
31 ])->all();
32
33 $changes = DB::transaction(function () use ($user, $pivotData) {
34 return $user->roles()->syncWithoutDetaching($pivotData);
35 });
36
37 $attached = $roles->whereIn('id', $changes['attached']);
38
39 $attached->each(function (Role $role) use ($user) {
40 RoleAssigned::dispatch($user, $role);
41 });
42
43 return $attached;
44 }
45}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Scoping the role query to the user's department prevents assigning roles that don't belong to their organizational unit.
- 2syncWithoutDetaching adds new pivot rows while leaving existing ones untouched, making the operation safe to retry.
- 3Firing events only for the roles actually attached avoids duplicate notifications on repeated calls.
Related explainers
php
<?php namespace App\Models\Concerns;
Building soft deletes as an Eloquent trait in Laravel
traits
soft-delete
global-scope
Intermediate
10 steps
go
package handlers type WebhookEvent struct { ID string `json:"id"`
Verifying and processing webhooks in Gin
webhooks
hmac
idempotency
Intermediate
9 steps
php
<?php namespace App\Services;
Merging overlapping busy time blocks in PHP
interval-merging
sorting
datetime
Intermediate
8 steps
php
<?php namespace App\Listeners;
How event subscribers group listeners in Laravel
event-driven
subscribers
queues
Intermediate
6 steps
php
<?php namespace App\Services;
How user impersonation works in Laravel
authentication
authorization
session
Intermediate
8 steps
php
class OrderReceiptController extends Controller { public function store(Request $request, Order $order) {
Handling receipt uploads in a Laravel controller
file-upload
validation
authorization
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/idempotent-role-assignment-in-laravel-explained-php-719f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.