laravel-permission-development
npx skills add https://github.com/spatie/laravel-permission --skill laravel-permission-development
Agent 安装分布
Skill 文档
Laravel Permission Development
When to use this skill
Use this skill when working with authorization, roles, permissions, access control, middleware guards, or Blade permission directives using spatie/laravel-permission.
Core Concepts
- Users have Roles, Roles have Permissions, Apps check Permissions (not Roles).
- Direct permissions on users are an anti-pattern; assign permissions to roles instead.
- Use
$user->can('permission-name')for all authorization checks (supports Super Admin via Gate). - The
HasRolestrait (which includesHasPermissions) is added to User models.
Setup
Add the HasRoles trait to your User model:
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
}
Creating Roles and Permissions
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
$role = Role::create(['name' => 'writer']);
$permission = Permission::create(['name' => 'edit articles']);
// findOrCreate is idempotent (safe for seeders)
$role = Role::findOrCreate('writer', 'web');
$permission = Permission::findOrCreate('edit articles', 'web');
Assigning Roles and Permissions
// Assign roles to users
$user->assignRole('writer');
$user->assignRole('writer', 'admin');
$user->assignRole(['writer', 'admin']);
$user->syncRoles(['writer', 'admin']); // replaces all
$user->removeRole('writer');
// Assign permissions to roles (preferred)
$role->givePermissionTo('edit articles');
$role->givePermissionTo(['edit articles', 'delete articles']);
$role->syncPermissions(['edit articles', 'delete articles']);
$role->revokePermissionTo('edit articles');
// Reverse assignment
$permission->assignRole('writer');
$permission->syncRoles(['writer', 'editor']);
$permission->removeRole('writer');
Checking Roles and Permissions
// Permission checks (preferred - supports Super Admin via Gate)
$user->can('edit articles');
$user->canAny(['edit articles', 'delete articles']);
// Direct package methods (bypass Gate, no Super Admin support)
$user->hasPermissionTo('edit articles');
$user->hasAnyPermission(['edit articles', 'publish articles']);
$user->hasAllPermissions(['edit articles', 'publish articles']);
$user->hasDirectPermission('edit articles');
// Role checks
$user->hasRole('writer');
$user->hasAnyRole(['writer', 'editor']);
$user->hasAllRoles(['writer', 'editor']);
$user->hasExactRoles(['writer', 'editor']);
// Get assigned roles and permissions
$user->getRoleNames(); // Collection of role name strings
$user->getPermissionNames(); // Collection of permission name strings
$user->getDirectPermissions(); // Direct permissions only
$user->getPermissionsViaRoles(); // Inherited via roles
$user->getAllPermissions(); // Both direct and inherited
Query Scopes
$users = User::role('writer')->get();
$users = User::withoutRole('writer')->get();
$users = User::permission('edit articles')->get();
$users = User::withoutPermission('edit articles')->get();
Middleware
Register middleware aliases in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
})
Use in routes (pipe | for OR logic):
Route::middleware(['permission:edit articles'])->group(function () { ... });
Route::middleware(['role:manager|writer'])->group(function () { ... });
Route::middleware(['role_or_permission:manager|edit articles'])->group(function () { ... });
// With specific guard
Route::middleware(['role:manager,api'])->group(function () { ... });
For single permissions, Laravel’s built-in can middleware also works:
Route::middleware(['can:edit articles'])->group(function () { ... });
Blade Directives
Prefer @can (permission-based) over @role (role-based):
@can('edit articles')
{{-- User can edit articles (supports Super Admin) --}}
@endcan
@canany(['edit articles', 'delete articles'])
{{-- User can do at least one --}}
@endcanany
@role('admin')
{{-- Only use for super-admin type checks --}}
@endrole
@hasanyrole('writer|admin')
{{-- Has writer or admin --}}
@endhasanyrole
Super Admin
Use Gate::before in AppServiceProvider::boot():
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::before(function ($user, $ability) {
return $user->hasRole('Super Admin') ? true : null;
});
}
This makes $user->can() and @can always return true for Super Admins. Must return null (not false) to allow normal checks for other users.
Policies
Use $user->can() inside policy methods to check permissions:
class PostPolicy
{
public function update(User $user, Post $post): bool
{
if ($user->can('edit all posts')) {
return true;
}
return $user->can('edit own posts') && $user->id === $post->user_id;
}
}
Enums
enum RolesEnum: string
{
case WRITER = 'writer';
case EDITOR = 'editor';
}
enum PermissionsEnum: string
{
case EDIT_POSTS = 'edit posts';
case DELETE_POSTS = 'delete posts';
}
// Creation requires ->value
Permission::findOrCreate(PermissionsEnum::EDIT_POSTS->value, 'web');
// Most methods accept enums directly
$user->assignRole(RolesEnum::WRITER);
$user->hasRole(RolesEnum::WRITER);
$role->givePermissionTo(PermissionsEnum::EDIT_POSTS);
$user->hasPermissionTo(PermissionsEnum::EDIT_POSTS);
Seeding
Always flush the permission cache when seeding:
class RolesAndPermissionsSeeder extends Seeder
{
public function run(): void
{
// Reset cache
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// Create permissions
Permission::findOrCreate('edit articles', 'web');
Permission::findOrCreate('delete articles', 'web');
// Create roles and assign permissions
Role::findOrCreate('writer', 'web')
->givePermissionTo(['edit articles']);
Role::findOrCreate('admin', 'web')
->givePermissionTo(Permission::all());
}
}
Teams (Multi-Tenancy)
Enable in config/permission.php before running migrations:
'teams' => true,
Set the active team in middleware:
setPermissionsTeamId($teamId);
When switching teams, unset cached relations:
$user->unsetRelation('roles')->unsetRelation('permissions');
Events
Enable in config/permission.php:
'events_enabled' => true,
Available events: RoleAttachedEvent, RoleDetachedEvent, PermissionAttachedEvent, PermissionDetachedEvent in the Spatie\Permission\Events namespace.
Performance
- Permissions are cached automatically. The cache is flushed when roles/permissions change via package methods.
- After direct DB operations, flush manually:
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions() - For bulk seeding, use
Permission::insert()for speed, but flush the cache afterward.