Before and after hooks on gates
Concept
Role-based access control (RBAC) assigns permissions to roles, then assigns roles to users. This is a proven pattern for most applications — it's simpler than capability-based systems and fits most business requirements.
Simple RBAC in Laravel: Gates and Policies check the user's role attribute. A User has a role column (admin, editor, user). Gates check $user->role === 'admin'. No package needed for most cases.
Hierarchical roles with inheritance: admin > editor > user. Each role can do everything the role below it can. Implement with an array hierarchy.
Database-driven RBAC: Roles and permissions in DB. users → role_user pivot → roles → role_permission pivot → permissions. Flexible but adds complexity. Check permissions with a method: $user->hasPermission('posts.update').
spatie/laravel-permission: The most popular RBAC package. Provides Role and Permission models, pivot tables, blade directives @role, @hasrole, @can. Adds HasRoles trait to User model. Methods: assignRole(), hasRole(), givePermissionTo(), hasPermissionTo().
When to use a package vs hand-rolling: Hand-roll if your role model is simple (2-3 roles, static). Use spatie/laravel-permission when roles and permissions change at runtime (configured by admins in a UI).
Code Example
<?php
// Simple role column approach (no package)
// users table: role VARCHAR(20) DEFAULT 'user'
class User extends \Illuminate\Foundation\Auth\User
{
public function isAdmin(): bool { return $this->role === 'admin'; }
public function isEditor(): bool { return in_array($this->role, ['admin', 'editor']); }
public function hasRole(string|array $roles): bool
{
return in_array($this->role, (array) $roles);
}
}
// Gates using roles
Gate::define('manage-users', fn(User $user) => $user->isAdmin());
Gate::define('create-posts', fn(User $user) => $user->isEditor());
Gate::define('edit-post', fn(User $user, Post $post) =>
$user->isAdmin() || ($user->isEditor() && $user->id === $post->user_id)
);
// Database-driven RBAC (without package)
// Tables: users, roles, permissions, role_user, permission_role
class User extends \Illuminate\Foundation\Auth\User
{
public function roles(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Role::class);
}
public function hasPermission(string $permission): bool
{
return $this->roles->flatMap->permissions->contains('slug', $permission);
}
}
Gate::define('posts.create', fn(User $user) => $user->hasPermission('posts.create'));
// With spatie/laravel-permission (package)
// composer require spatie/laravel-permission
$user->assignRole('editor');
$user->givePermissionTo('edit articles');
$user->hasRole('editor'); // true
$user->can('edit articles'); // true (via policy gate auto-registration)
// In blade: @role('admin') ... @endrole
// In middleware: 'role:admin' or 'permission:edit articles'
// Caching permissions — important for performance
// spatie caches permissions. Clear with:
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();