88
99A unified authorization management system for Laravel that brings together roles, permissions, and feature flags into a
1010single, type-safe API. Built
11- on [ Spatie Laravel Permission] ( https://github.com/spatie/laravel-permission ) , [ Laravel Pennant] ( https://laravel.com/docs/pennant ) ,
11+ on [ Spatie Laravel Permission] ( https://github.com/spatie/laravel-permission ) . Integrates
12+ with [ Laravel Pennant] ( https://laravel.com/docs/pennant )
1213and [ Laravel Hoist] ( https://github.com/offload-project/laravel-hoist ) .
1314
1415## Features
@@ -30,10 +31,14 @@ and [Laravel Hoist](https://github.com/offload-project/laravel-hoist).
3031
3132- PHP 8.4+
3233- Laravel 11+
33- - Laravel Pennant 1.0+
34- - Laravel Hoist 1.0+
3534- Spatie Laravel Permission 6.0+
36- - Spatie Laravel Data 4.0+
35+
36+ ### Works With
37+
38+ Mandate integrates with these packages for optional feature flag support:
39+
40+ - [ Laravel Pennant] ( https://laravel.com/docs/pennant ) 1.0+ - Gate permissions/roles behind feature flags
41+ - [ Laravel Hoist] ( https://github.com/offload-project/laravel-hoist ) 1.0+ - Enhanced feature flag management
3742
3843## Installation
3944
@@ -47,14 +52,54 @@ Publish the configuration:
4752
4853``` bash
4954php artisan vendor:publish --tag=mandate-config
55+ ```
56+
57+ Optionally, publish migrations if you want to store ` set ` , ` label ` , or ` description ` columns:
58+
59+ ``` bash
5060php artisan vendor:publish --tag=mandate-migrations
5161```
5262
5363## Quick Start
5464
55- ### 1. Create Permission Classes
65+ Define roles and permissions directly in config - no classes required:
66+
67+ ``` php
68+ // config/mandate.php
69+ 'role_permissions' => [
70+ 'viewer' => [
71+ 'users.view',
72+ 'posts.view',
73+ ],
74+
75+ 'editor' => [
76+ 'users.view',
77+ 'posts.view',
78+ 'posts.create',
79+ 'posts.update',
80+ ],
81+
82+ 'admin' => [
83+ 'users.*', // Wildcard: all user permissions
84+ 'posts.*', // Wildcard: all post permissions
85+ ],
86+ ],
87+ ```
88+
89+ Then sync to database:
90+
91+ ``` bash
92+ php artisan mandate:sync --seed
93+ ```
94+
95+ That's it! For type-safe constants and IDE autocompletion,
96+ see [ Defining Classes] ( #defining-roles-and-permissions-using-classes ) .
97+
98+ ## Defining Roles and Permissions Using Classes
99+
100+ For larger applications, define permissions and roles as classes for type-safety and IDE support.
56101
57- > These are OPTIONAL but useful for use throughout the codebase)
102+ ### Permission Classes
58103
59104``` bash
60105php artisan mandate:permission UserPermissions --set=users
@@ -70,23 +115,23 @@ use OffloadProject\Mandate\Attributes\PermissionsSet;
70115final class UserPermissions
71116{
72117 #[Label('View Users')]
73- public const string VIEW = 'view users';
118+ public const string VIEW = 'users.view ';
74119
75120 #[Label('Create Users')]
76- public const string CREATE = 'create users';
121+ public const string CREATE = 'users.create ';
77122
78123 #[Label('Update Users')]
79- public const string UPDATE = 'update users';
124+ public const string UPDATE = 'users.update ';
80125
81126 #[Label('Delete Users')]
82- public const string DELETE = 'delete users';
127+ public const string DELETE = 'users.delete ';
83128
84129 #[Label('Export Users'), Description('Export user data to CSV')]
85- public const string EXPORT = 'export users';
130+ public const string EXPORT = 'users.export ';
86131}
87132```
88133
89- ### 2. Create Role Classes
134+ ### Role Classes
90135
91136``` bash
92137php artisan mandate:role SystemRoles --set=system
@@ -115,7 +160,10 @@ final class SystemRoles
115160}
116161```
117162
118- ### 3. Map Roles to Permissions (Config)
163+ ### Map Roles to Permissions (Config)
164+
165+ With inheritance defined in role classes, only specify * direct* permissions - inherited permissions resolve
166+ automatically:
119167
120168``` php
121169// config/mandate.php
@@ -124,28 +172,29 @@ use App\Permissions\PostPermissions;
124172use App\Roles\SystemRoles;
125173
126174'role_permissions' => [
127- SystemRoles::ADMINISTRATOR => [
128- UserPermissions::class, // All user permissions
129- PostPermissions::class, // All post permissions
175+ // Viewer gets base permissions
176+ SystemRoles::VIEWER => [
177+ UserPermissions::VIEW,
178+ PostPermissions::VIEW,
130179 ],
131180
181+ // Editor inherits Viewer permissions, only add Editor-specific
132182 SystemRoles::EDITOR => [
133- UserPermissions::VIEW,
134- PostPermissions::VIEW,
135183 PostPermissions::CREATE,
136184 PostPermissions::UPDATE,
137185 ],
138186
139- SystemRoles::VIEWER => [
140- UserPermissions::VIEW,
141- PostPermissions::VIEW,
187+ // Administrator inherits Editor (and transitively Viewer)
188+ SystemRoles::ADMINISTRATOR => [
189+ UserPermissions::class, // All user permissions
190+ PostPermissions::DELETE,
142191 ],
143192],
144193```
145194
146- ### 4. Define Feature Gates (Optional)
195+ ## Feature Gates (Optional)
147196
148- Features control which permissions/roles are available:
197+ Features control which permissions/roles are available (requires [ Pennant or Hoist ] ( #works-with ) ) :
149198
150199``` php
151200// app/Features/ExportFeature.php
@@ -176,7 +225,7 @@ class ExportFeature
176225}
177226```
178227
179- ### 5. Sync to Database
228+ ## Sync to Database
180229
181230``` bash
182231# Initial setup - seeds role permissions from config
@@ -630,6 +679,103 @@ php artisan mandate:sync --seed
630679
631680This means the database role will have all permissions (direct + inherited) assigned via Spatie.
632681
682+ ## Wildcard Permissions
683+
684+ Mandate supports wildcard patterns for permission matching, allowing flexible permission checks and role configuration.
685+
686+ ### Wildcard Patterns
687+
688+ The ` * ` wildcard matches a single segment (does not cross dots):
689+
690+ | Pattern | Matches | Does Not Match |
691+ | ----------------| -----------------------------------------| ------------------------------------|
692+ | ` users.* ` | ` users.view ` , ` users.create ` | ` posts.view ` , ` users.admin.view ` |
693+ | ` *.view ` | ` users.view ` , ` posts.view ` | ` users.create ` , ` admin.users.view ` |
694+ | ` users.*.view ` | ` users.admin.view ` , ` users.public.view ` | ` users.view ` , ` posts.admin.view ` |
695+
696+ ### Using Wildcards in Permission Checks
697+
698+ Check if a user has any permission matching a pattern:
699+
700+ ``` php
701+ use OffloadProject\Mandate\Facades\Mandate;
702+
703+ // Check if user has any users.* permission
704+ if (Mandate::can($user, 'users.*')) {
705+ // User has at least one permission like users.view, users.create, etc.
706+ }
707+
708+ // Check if user has any *.view permission
709+ if (Mandate::can($user, '*.view')) {
710+ // User has at least one view permission (users.view, posts.view, etc.)
711+ }
712+ ```
713+
714+ ### Using Wildcards in Config
715+
716+ Assign multiple permissions to a role using wildcards:
717+
718+ ``` php
719+ // config/mandate.php
720+ 'role_permissions' => [
721+ 'viewer' => [
722+ '*.view', // All view permissions (users.view, posts.view, etc.)
723+ ],
724+
725+ 'user-admin' => [
726+ 'users.*', // All user permissions
727+ 'reports.view', // Plus specific permission
728+ ],
729+
730+ 'super-admin' => [
731+ UserPermissions::class, // All from class
732+ '*.delete', // Plus all delete permissions
733+ ],
734+ ],
735+ ```
736+
737+ Wildcards are expanded at sync time to the actual matching permissions.
738+
739+ ### Using Wildcards in Middleware
740+
741+ Protect routes with wildcard permission patterns:
742+
743+ ``` php
744+ use OffloadProject\Mandate\Http\Middleware\MandatePermission;
745+
746+ // String-based
747+ Route::get('/users', UserController::class)
748+ ->middleware('mandate.permission:users.*');
749+
750+ Route::get('/reports', ReportController::class)
751+ ->middleware('mandate.permission:*.view');
752+
753+ // Using the helper
754+ Route::get('/users', UserController::class)
755+ ->middleware(MandatePermission::using('users.*'));
756+ ```
757+
758+ ### Dot-Notation Permissions
759+
760+ For best wildcard support, use dot-notation for permission names:
761+
762+ ``` php
763+ #[PermissionsSet('users')]
764+ final class UserPermissions
765+ {
766+ public const string VIEW = 'users.view';
767+ public const string CREATE = 'users.create';
768+ public const string UPDATE = 'users.update';
769+ public const string DELETE = 'users.delete';
770+ }
771+ ```
772+
773+ This naming convention enables powerful patterns:
774+
775+ - ` users.* ` - All user permissions
776+ - ` *.view ` - All view permissions across modules
777+ - ` *.delete ` - All delete permissions (for admin roles)
778+
633779## Attributes
634780
635781### Permission Classes
@@ -643,13 +789,13 @@ This means the database role will have all permissions (direct + inherited) assi
643789
644790### Role Classes
645791
646- | Attribute | Target | Description |
647- | -------------------------------- | -------------------| -- --------------------------------------------|
648- | ` #[RoleSet('name')] ` | Class | Groups roles together (required) |
649- | ` #[Label('Human Name')] ` | Constant | Human-readable label |
650- | ` #[Description('Details')] ` | Constant | Detailed description |
651- | ` #[Guard('web')] ` | Class or Constant | Auth guard to use |
652- | ` #[Inherits('parent', ...)] ` | Constant | Parent role(s) to inherit permissions from |
792+ | Attribute | Target | Description |
793+ | ------------------------------| -------------------| --------------------------------------------|
794+ | ` #[RoleSet('name')] ` | Class | Groups roles together (required) |
795+ | ` #[Label('Human Name')] ` | Constant | Human-readable label |
796+ | ` #[Description('Details')] ` | Constant | Detailed description |
797+ | ` #[Guard('web')] ` | Class or Constant | Auth guard to use |
798+ | ` #[Inherits('parent', ...)] ` | Constant | Parent role(s) to inherit permissions from |
653799
654800## Artisan Commands
655801
0 commit comments