0

Before and after hooks on gates

Advanced5 min read·lv-17-005

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. usersrole_user pivot → rolesrole_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
<?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();