Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

t0berius's avatar
Level 13

spatie/laravel-permission (Cache user role)

Dear laravel artisans,

I'm using the package spatie/laravel-permission for RBAC inside my application. A middleware is running performing a simple role check:

public function handle($request, Closure $next)
{
    if (Auth::user()->hasRole('staff')) {
		//forward request...
    } else {
        abort(404);
    }
}

Beside of this middleware I'm using a simple method inside a blade template file like:

@if($user->canBeImpersonated())
	//rendering content
@endif

The canBeImpersonated() method from inside the User model is simple:

//it's impossible to impersonate staff members
public function canBeImpersonated()
{
    return !$this->hasRole('staff');
}

This will cause 2 SQL queries (doubled):

select `roles`.*, `model_has_roles`.`model_id` as `pivot_model_id`, `model_has_roles`.`role_id` as `pivot_role_id`, `model_has_roles`.`model_type` as `pivot_model_type` from `roles` inner join `model_has_roles` on `roles`.`id` = `model_has_roles`.`role_id` where `model_has_roles`.`model_id` in (4) and `model_has_roles`.`model_type` = 'App\Models\User'
select `roles`.*, `model_has_roles`.`model_id` as `pivot_model_id`, `model_has_roles`.`role_id` as `pivot_role_id`, `model_has_roles`.`model_type` as `pivot_model_type` from `roles` inner join `model_has_roles` on `roles`.`id` = `model_has_roles`.`role_id` where `model_has_roles`.`model_id` in (4) and `model_has_roles`.`model_type` = 'App\Models\User'

Is there an elegant way to prevent this from happening?

0 likes
2 replies
LaryAI's avatar
Level 58

To prevent the duplicate SQL queries when checking user roles with the spatie/laravel-permission package, you can cache the roles for the user. This way, the roles are only fetched once and reused for subsequent checks.

Here’s how you can implement caching for user roles:

  1. Create a method to cache roles in the User model:
use Illuminate\Support\Facades\Cache;

public function getCachedRoles()
{
    return Cache::remember("user_roles_{$this->id}", now()->addMinutes(10), function () {
        return $this->roles;
    });
}
  1. Update the hasRole and canBeImpersonated methods to use the cached roles:
public function hasRole($role)
{
    return $this->getCachedRoles()->contains('name', $role);
}

public function canBeImpersonated()
{
    return !$this->hasRole('staff');
}
  1. Update the middleware to use the cached roles:
public function handle($request, Closure $next)
{
    if (Auth::user()->hasRole('staff')) {
        // forward request...
    } else {
        abort(404);
    }

    return $next($request);
}
  1. Clear the cache when roles are updated:

You need to ensure that the cache is cleared whenever the roles are updated. You can do this by adding an observer to the User model or directly in the methods where roles are assigned or removed.

public function assignRole(...$roles)
{
    $this->roles()->sync($roles);
    Cache::forget("user_roles_{$this->id}");
    return $this;
}

public function removeRole(...$roles)
{
    $this->roles()->detach($roles);
    Cache::forget("user_roles_{$this->id}");
    return $this;
}

By implementing these changes, you will cache the roles for each user and avoid duplicate SQL queries, improving the performance of your application.

t0berius's avatar
Level 13

Is this common sense to override the hasRole method provided by the package itself? I'm unsure about this.

Reimplementing all the assignRole and removeRole methods feels so wrong.

Please or to participate in this conversation.