Philsquare Permissions
A Laravel package for managing role-based permissions through policies. Define permissions declaratively in your policies and sync them to your database with a single command.
Requirements
Installation
composer require philsquare/permissions
The service provider is auto-registered via Laravel's package discovery.
Setup Spatie Permissions
If you haven't already, publish and run the Spatie migrations:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
Add the HasRoles trait to your User model:
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
}
Quick Start
1. Create a Policy
Use the artisan command with the --withPermissions flag:
php artisan make:policy PostPolicy --model=Post --withPermissions
Or add permissions to an existing policy by extending BasePolicy:
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Philsquare\Permissions\BasePolicy;
class PostPolicy extends BasePolicy
{
public function rolePermissions(): array
{
return [
'admin' => $this->permissions()->all(),
'editor' => $this->permissions()->crud(),
'viewer' => $this->permissions()->only(['viewAny', 'view']),
];
}
public function viewAny(User $user): bool
{
return true;
}
public function view(User $user, Post $post): bool
{
return true;
}
public function create(User $user): bool
{
return true;
}
public function update(User $user, Post $post): bool
{
return true;
}
public function delete(User $user, Post $post): bool
{
return true;
}
}
2. Sync Permissions to Database
Run the refresh command whenever you add or modify permissions:
php artisan permissions:refresh
This command:
- Scans all policies in
app/Policies/ that extend BasePolicy
- Creates any missing roles
- Creates any missing permissions
- Syncs role-permission assignments
3. Assign Roles to Users
Use Spatie's methods to assign roles:
$user->assignRole('editor');
4. Check Permissions
Use Laravel's built-in authorization:
// In controllers
$this->authorize('update', $post);
// Using Gate
Gate::authorize('update', $post);
// On User model
$user->can('update', $post);
// In Blade
@can('update', $post)
<button>Edit</button>
@endcan
How It Works
Permission Naming Convention
Permissions are stored in kebab-case format: {model}:{action}
| Policy Method | Database Permission |
|---|
PostPolicy::viewAny() | post:view-any |
PostPolicy::create() | post:create |
PostPolicy::forceDelete() | post:force-delete |
PurchaseOrderPolicy::updateEta() | purchase-order:update-eta |
The before() Hook
When you extend BasePolicy, the before() method automatically checks if the user's roles have the required permission. If the permission exists, it returns null (allowing the policy method to execute). If not, it returns false (denying access).
This means your policy methods define the logic for when an action should be allowed, and the role-permission mapping controls who can attempt it.
Policy Method Return Values
Your policy methods should return true or false based on business logic:
public function update(User $user, Post $post): bool
{
// Only allow updates if post is draft OR user has force-update permission
return $post->status === 'draft' || $user->can('force-update', $post);
}
The before() hook runs first. If the user lacks the permission, they're denied immediately. If they have the permission, your method's logic determines the final result.
Permission Helpers
The permissions() method provides helpers for building permission lists:
all()
Returns all public methods from the policy (excluding system methods):
public function rolePermissions(): array
{
return [
'admin' => $this->permissions()->all(),
];
}
crud(array $additional = [])
Returns standard CRUD methods plus any additional methods:
// Returns: viewAny, view, create, update, delete
'editor' => $this->permissions()->crud(),
// Returns: viewAny, view, create, update, delete, publish, archive
'editor' => $this->permissions()->crud(['publish', 'archive']),
only(array $methods)
Returns only the specified methods:
'viewer' => $this->permissions()->only(['viewAny', 'view']),
except(array $methods)
Returns all methods except the specified ones:
'editor' => $this->permissions()->except(['delete', 'forceDelete']),
Full Example
<?php