amdadulhaq / guard-laravel
Guard is Role and Permission management system for Laravel
Fund package maintenance!
amdad121
Installs: 2 952
Dependents: 0
Suggesters: 0
Security: 0
Stars: 12
Watchers: 1
Forks: 2
Open Issues: 0
pkg:composer/amdadulhaq/guard-laravel
Requires
- php: ^8.2|^8.3|^8.4|^8.5
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- driftingly/rector-laravel: ^1.0|^2.0
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0|^4.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0|^4.0
README
A powerful, flexible, and developer-friendly role and permission management system for Laravel applications.
๐ Quick Start
Get up and running in 5 minutes:
Upgrading from an older version? Check the Upgrade Guide for detailed migration instructions.
1. Install via Composer
composer require amdadulhaq/guard-laravel
2. Publish and run migrations
php artisan vendor:publish --tag="guard-migrations"
php artisan migrate
3. Setup your User model
<?php namespace App\Models; use AmdadulHaq\Guard\Contracts\User as UserContract; use AmdadulHaq\Guard\Concerns\HasRoles; use AmdadulHaq\Guard\Concerns\HasPermissions; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements UserContract { use HasRoles; use HasPermissions; }
4. Create your first role and permission
php artisan guard:create-role admin --label="Administrator" php artisan guard:create-permission users.create --label="Create Users"
5. Protect your routes
Route::middleware('role:admin')->get('/admin', [AdminController::class, 'index']);
โจ Features
- ๐ฏ Modern PHP & Laravel - Built for PHP 8.2+ and Laravel 10/11/12
- ๐ Flexible Permission System - Users can have permissions via roles AND directly assigned
- ๐ญ Wildcard Permissions - Use
posts.*to match all post-related permissions - โก Smart Caching - Automatic cache invalidation for optimal performance
- ๐ Laravel Gate Integration - Native
@can,@canany,@cannotsupport - ๐ก๏ธ Middleware Protection -
role,permission, androle_or_permissionmiddleware - ๐จ Blade Directives -
@role,@hasrole,@hasanyrole,@hasallroles - ๐ฆ Type-Safe Enums - IDE-friendly
PermissionTypeandCacheKeyenums - ๐ฐ Guarded Roles - Protect critical roles from accidental deletion
- ๐ Permission Groups - Organize permissions by resource
- ๐จ Interactive Commands - Laravel Prompts for creating roles/permissions
- ๐งน Clean Architecture - Separated concerns with traits and contracts
- ๐งช Developer Tools - Pint, Pest, Rector, and Larastan included
๐ Table of Contents
- Installation
- Upgrade Guide
- Configuration
- Usage
- Models Reference
- Exceptions
- Caching
- Database Structure
- Enums
- Development
- Troubleshooting
- FAQ
๐ฆ Installation
Requirements
- PHP: 8.2, 8.3, 8.4, or 8.5
- Laravel: 10.x, 11.x, or 12.x
- Database: MySQL 5.7+, PostgreSQL 9.6+, SQLite 3.8+, or SQL Server 2017+
Step 1: Install via Composer
composer require amdadulhaq/guard-laravel
Step 2: Publish Migrations
php artisan vendor:publish --tag="guard-migrations"
php artisan migrate
This creates 4 tables:
roles- Role definitionspermissions- Permission definitionspermission_role- Role-permission relationshipsrole_user- User-role relationships
Step 3: Configure User Model
<?php namespace App\Models; use AmdadulHaq\Guard\Contracts\User as UserContract; use AmdadulHaq\Guard\Concerns\HasRoles; use AmdadulHaq\Guard\Concerns\HasPermissions; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements UserContract { use HasRoles; use HasPermissions; }
Step 4: (Optional) Publish Config
php artisan vendor:publish --tag="guard-config"
โ๏ธ Configuration
The config/guard.php file:
return [ 'models' => [ 'user' => \App\Models\User::class, 'role' => \AmdadulHaq\Guard\Models\Role::class, 'permission' => \AmdadulHaq\Guard\Models\Permission::class, ], 'tables' => [ 'roles' => 'roles', 'permissions' => 'permissions', ], 'cache' => [ 'enabled' => env('GUARD_CACHE_ENABLED', true), 'roles_duration' => (int) env('GUARD_ROLES_CACHE_DURATION', 3600), 'permissions_duration' => (int) env('GUARD_PERMISSIONS_CACHE_DURATION', 3600), ], 'middleware' => [ 'role' => 'role', 'permission' => 'permission', 'role_or_permission' => 'role_or_permission', ], 'wildcard' => [ 'enabled' => env('GUARD_WILDCARD_ENABLED', true), ], ];
๐ฏ Usage
User Setup
Users implement the UserContract which combines Roles and Permissions contracts:
use AmdadulHaq\Guard\Contracts\User as UserContract; use AmdadulHaq\Guard\Concerns\HasRoles; use AmdadulHaq\Guard\Concerns\HasPermissions; class User extends Authenticatable implements UserContract { use HasRoles; // Role management methods use HasPermissions; // Permission management methods }
Creating Roles
use AmdadulHaq\Guard\Models\Role; // Create a role $adminRole = Role::create([ 'name' => 'administrator', 'label' => 'Administrator', 'description' => 'Full system access', 'is_guarded' => true, // Protected from deletion ]); // Create via command // php artisan guard:create-role moderator --label="Moderator"
Role Model Methods:
$role->getName(); // Get role name $role->isProtectedRole(); // Check if guarded $role->getPermissionNames(); // Get all permission names $role->users; // Get users with this role // Query scopes Role::guarded()->get(); // Only guarded roles Role::unguarded()->get(); // Only unguarded roles
Creating Permissions
use AmdadulHaq\Guard\Models\Permission; // Simple permission Permission::create([ 'name' => 'users.create', 'label' => 'Create Users', 'description' => 'Can create new users', 'group' => 'users', // For organization ]); // Wildcard permission (auto-sets is_wildcard = true) Permission::create([ 'name' => 'posts.*', 'label' => 'Manage All Posts', 'group' => 'posts', ]); // Create via command // php artisan guard:create-permission users.delete --label="Delete Users"
Permission Model Methods:
$permission->getName(); // Get permission name $permission->getLabel(); // Get human-readable label $permission->getDescription(); // Get description $permission->isWildcard(); // Check if wildcard (e.g., posts.*) $permission->getGroup(); // Get group (e.g., 'users' from 'users.create') $permission->getType(); // Get PermissionType enum (e.g., PermissionType::CREATE) $permission->roles; // Get roles with this permission // Query scopes Permission::wildcard()->get(); // Only wildcard permissions Permission::byGroup('users')->get(); // Permissions in users group
Wildcard Permissions
Wildcard permissions automatically match all sub-permissions:
// Create wildcard permission Permission::create(['name' => 'posts.*']); // Assign to role $role->givePermissionTo('posts.*'); // Now user can do all of these: $user->hasPermission('posts.create'); // true $user->hasPermission('posts.update'); // true $user->hasPermission('posts.delete'); // true $user->hasPermission('posts.publish'); // true
The is_wildcard boolean is automatically set when the name ends with *.
Role Management
Assigning Roles:
// Single role $user->assignRole('administrator'); $user->assignRole($roleModel); // Multiple roles $user->assignRole(['administrator', 'editor']); // Sync (replaces all) $user->syncRoles(['administrator', 'editor']); $user->syncRoles([$role1->id, $role2->id]); // Sync without detaching existing $user->syncRolesWithoutDetaching(['moderator']); // Revoke $user->revokeRole('editor'); $user->revokeRole($roleModel); $user->revokeRoles(); // Revoke all
Checking Roles:
// Single role $user->hasRole('administrator'); // true/false // Multiple roles $user->hasAllRoles(['admin', 'editor']); // Must have ALL $user->hasAnyRole(['admin', 'moderator']); // Must have ANY // Get role names $user->getRoleNames(); // ['administrator', 'editor']
Permission Management
Assigning to Roles:
// Single permission $role->givePermissionTo('users.create'); $role->givePermissionTo($permissionModel); // Multiple permissions $role->givePermissionTo(['users.create', 'users.edit', 'users.delete']); // Sync (replaces all) $role->syncPermissions(['users.create', 'users.edit']); $role->syncPermissions([$perm1->id, $perm2->id]); // Revoke $role->revokePermissionTo('users.delete'); $role->revokePermissionTo($permissionModel); $role->revokeAllPermissions();
Checking Role Permissions:
$role->hasPermissionTo('users.edit'); // Check if role has permission $role->getPermissionNames(); // Get all permission names
Direct User Permissions
Users can have permissions directly in addition to permissions from roles:
// Give direct permission $user->givePermissionTo('posts.delete'); // Multiple permissions $user->givePermissionTo(['posts.create', 'posts.update']); // Sync (replaces all direct permissions) $user->syncPermissions(['posts.create', 'posts.update']); // Revoke $user->revokePermissionTo('posts.delete'); $user->revokeAllPermissions();
Checking User Permissions:
// Check by name (checks roles + direct permissions) $user->hasPermission('users.create'); // Check by model $user->hasPermission($permissionModel); // Wildcard matching $user->hasPermission('posts.*'); // Get all permissions (roles + direct) $user->getPermissions(); // Get permission names array $user->getPermissionNames(); // ['users.create', 'users.edit', 'posts.delete']
Checking Access
Role Checking:
if ($user->hasRole('administrator')) { // User has administrator role } if ($user->hasAllRoles(['admin', 'editor'])) { // User has both roles } if ($user->hasAnyRole(['admin', 'moderator'])) { // User has at least one role } // Get all role names $user->getRoleNames(); // ['administrator', 'editor']
Permission Checking:
if ($user->hasPermission('users.create')) { // User can create users } if ($user->hasPermission('posts.*')) { // User has wildcard permission for posts }
Middleware
All middleware supports multiple values (requires ANY):
// Role middleware Route::middleware('role:administrator')->get('/admin', [AdminController::class, 'index']); // Multiple roles (requires ANY) Route::middleware('role:admin,editor')->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); }); // Permission middleware Route::middleware('permission:users.create')->post('/users', [UserController::class, 'store']); // Multiple permissions (requires ANY) Route::middleware('permission:users.create,users.edit')->put('/users/{id}', [UserController::class, 'update']); // Role OR permission middleware Route::middleware('role_or_permission:admin,users.create')->get('/users', [UserController::class, 'index']); // Multiple role_or_permission Route::middleware('role_or_permission:admin,editor,posts.manage')->group(function () { Route::post('/manage', [Controller::class, 'handle']); });
Gate Integration
The package automatically registers Gates for all permissions and roles:
// In controllers public function store(Request $request) { $this->authorize('users.create'); // User can create users } // Using Gate facade use Illuminate\Support\Facades\Gate; if (Gate::allows('users.create')) { // Allowed } if (Gate::denies('users.delete')) { abort(403, 'Permission denied'); } // Check for specific user if (Gate::forUser($otherUser)->allows('posts.edit')) { // That user can edit posts } // Authorize roles $this->authorize('administrator');
Blade Directives
Guard provides custom Blade directives for role checking, in addition to Laravel's built-in @can directives:
Custom Role Directives:
@role('administrator') <div class="admin-panel"> <h1>Admin Dashboard</h1> </div> @endrole @hasrole('editor') <p>Editor content here</p> @endhasrole @hasanyrole(['administrator', 'moderator']) <p>Content for admins or moderators</p> @endhasanyrole @hasallroles(['administrator', 'editor']) <p>Only for users with BOTH admin AND editor roles</p> @endhasallroles
Built-in Laravel Directives (via Gate integration):
@can('users.create') <a href="/users/create">Create User</a> @endcan @canany(['users.create', 'users.edit']) <p>You can manage users</p> @endcanany @cannot('users.delete') <p>You cannot delete users</p> @endcannot
Artisan Commands
Create a Role:
php artisan guard:create-role admin --label="Administrator" # With optional user assignment php artisan guard:create-role moderator --label="Moderator" --user=1
Create a Permission:
php artisan guard:create-permission users.create --label="Create Users" # With optional role assignment php artisan guard:create-permission posts.delete --label="Delete Posts" --role=1
Both commands use interactive Laravel Prompts if arguments are not provided.
Query Scopes
// Users with specific role User::whereHas('roles', function ($query) { $query->where('name', 'administrator'); })->get(); // Users with specific permission User::whereHas('roles.permissions', function ($query) { $query->where('name', 'users.create'); })->get(); // Note: The traits have protected scopeWithRoles and scopeWithPermissions // that can be used internally or extended in your User model
๐ Models Reference
User Model (via Traits)
HasRoles trait provides:
roles()- BelongsToMany relationshipassignRole($role)- Assign single or multiple rolessyncRoles(array $roles, bool $detach = true)- Sync rolessyncRolesWithoutDetaching(array $roles)- Sync without detachingrevokeRole($role)- Revoke specific rolerevokeRoles()- Revoke all rolesgetRoleNames()- Get all role nameshasRole($role)- Check single rolehasAllRoles(...$roles)- Check all roleshasAnyRole(...$roles)- Check any role
HasPermissions trait provides:
permissions()- BelongsToMany relationshipgivePermissionTo($permission)- Give single or multiple permissionssyncPermissions(array $permissions)- Sync permissionsrevokePermissionTo($permission)- Revoke specific permissionrevokeAllPermissions()- Revoke all permissionsgetPermissionNames()- Get all permission nameshasPermission($permission)- Check permission (by name or model)hasPermissionTo($permission)- Check if has specific permissiongetPermissions()- Get all permissions (from roles + direct)
Role Model
Properties:
name(string, unique)label(string, nullable)description(text, nullable)is_guarded(boolean)
Methods:
getName()- Get role nameisProtectedRole()- Check if guardedgetPermissionNames()- Get assigned permission namespermissions()- BelongsToMany to permissionsusers()- BelongsToMany to users
Scopes:
guarded()- Only guarded rolesunguarded()- Only unguarded roles
Permission Model
Properties:
name(string, unique)label(string, nullable)description(text, nullable)group(string, nullable, indexed)is_wildcard(boolean, auto-set)
Methods:
getName()- Get permission namegetLabel()- Get human-readable labelgetDescription()- Get descriptionisWildcard()- Check if wildcard patterngetGroup()- Get resource group (e.g., 'users')getType()- Get PermissionType enumroles()- BelongsToMany to rolesgiveRoleTo($role)- Give role to permissionsyncRoles(array $roles)- Sync rolesrevokeRole($role)- Revoke roleassignRole($role)- Alias for giveRoleTo
Scopes:
wildcard()- Only wildcard permissionsbyGroup($group)- Filter by group
๐จ Exceptions
use AmdadulHaq\Guard\Exceptions\PermissionDeniedException; use AmdadulHaq\Guard\Exceptions\RoleDoesNotExistException; use AmdadulHaq\Guard\Exceptions\PermissionDoesNotExistException; // Permission denied throw PermissionDeniedException::create('users.delete'); throw PermissionDeniedException::roleNotAssigned('administrator'); // Role not found throw RoleDoesNotExistException::named('admin'); throw RoleDoesNotExistException::withId(123); // Permission not found throw PermissionDoesNotExistException::named('users.delete'); throw PermissionDoesNotExistException::withId(456);
๐พ Caching
The package uses intelligent caching:
use AmdadulHaq\Guard\Facades\Guard; // Clear cache manually Guard::clearCache();
Cache is automatically cleared when:
- Roles or permissions are created/updated/deleted
- Role-permission relationships change
- User-role relationships change
Configuration:
'cache' => [ 'enabled' => true, 'roles_duration' => 3600, // 1 hour 'permissions_duration' => 3600, // 1 hour ],
๐๏ธ Database Structure
Roles Table
Schema::create('roles', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->string('label')->nullable(); $table->text('description')->nullable(); $table->boolean('is_guarded')->default(false); $table->timestamps(); });
Permissions Table
Schema::create('permissions', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->string('label')->nullable(); $table->text('description')->nullable(); $table->string('group')->nullable()->index(); $table->boolean('is_wildcard')->default(false); $table->timestamps(); });
Permission-Role Pivot
Schema::create('permission_role', function (Blueprint $table) { $table->foreignId('permission_id')->constrained()->cascadeOnDelete(); $table->foreignId('role_id')->constrained()->cascadeOnDelete(); $table->primary(['permission_id', 'role_id']); });
Role-User Pivot
Schema::create('role_user', function (Blueprint $table) { $table->foreignId('role_id')->constrained()->cascadeOnDelete(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->primary(['role_id', 'user_id']); });
๐ข Enums
PermissionType
use AmdadulHaq\Guard\Enums\PermissionType; PermissionType::CREATE->label(); // "Create" PermissionType::READ->label(); // "Read" PermissionType::WRITE->label(); // "Write" PermissionType::UPDATE->label(); // "Update" PermissionType::DELETE->label(); // "Delete" PermissionType::VIEW_ANY->label(); // "View any" PermissionType::VIEW->label(); // "View" PermissionType::RESTORE->label(); // "Restore" PermissionType::FORCE_DELETE->label(); // "Force delete" PermissionType::MANAGE->label(); // "Manage"
CacheKey
use AmdadulHaq\Guard\Enums\CacheKey; CacheKey::PERMISSIONS->value; // 'guard_permissions' CacheKey::ROLES->value; // 'guard_roles'
๐ ๏ธ Development
Code Quality Tools
# Rector (code refactoring) composer refactor composer refactor:check # Laravel Pint (code style) composer lint composer lint:check # Pest (testing) composer test composer test-coverage # Larastan (static analysis) composer analyse
Running Tests
# Run all tests composer test # With coverage composer test-coverage
๐ง Troubleshooting
Common Issues
Issue: Class 'AmdadulHaq\Guard\Concerns\HasRoles' not found
Solution:
composer dump-autoload
Issue: Target class [role] does not exist.
Solution:
php artisan config:clear
Issue: Permissions not being recognized
Solution:
php artisan cache:clear # Or php artisan tinker --execute="\AmdadulHaq\Guard\Facades\Guard::clearCache()"
Performance Tips
-
Keep caching enabled in production
-
Use wildcard permissions to reduce permission count
-
Filter at database level instead of loading all users:
// โ Good User::whereHas('roles', fn ($q) => $q->where('name', 'admin'))->get(); // โ Less efficient User::all()->filter(fn ($u) => $u->hasRole('admin'));
-
Eager load when needed:
User::with(['roles', 'roles.permissions'])->get();
โ FAQ
Q: Can I use this with Laravel Sanctum?
A: Yes! Guard works seamlessly with Sanctum and any auth system.
Q: Can users have permissions without roles?
A: Yes! Users can have both role-based AND direct permissions using givePermissionTo(), syncPermissions(), etc.
Q: How do wildcard permissions work?
A: Create a permission like posts.* and it automatically matches posts.create, posts.edit, etc.
Q: Can I customize table names?
A: Yes, publish the config and modify the tables section.
Q: Does it work with multiple guards?
A: Yes, it integrates with Laravel's authorization system.
Q: Is there a UI for managing roles?
A: Guard is backend-only. For a UI, consider Filament Shield or build your own.
Q: How do I create custom Blade directives?
A: Use Laravel's built-in @can, @canany, @cannot directives which work automatically via Gate integration.
Q: Can permissions be assigned to permissions?
A: No, permissions are assigned to roles, and users get permissions via roles or direct assignment.
๐ค Contributing
We welcome contributions! Please see CONTRIBUTING for details.
๐ Changelog
See CHANGELOG for recent changes.
๐ Security
Please review our security policy for reporting vulnerabilities.
๐ Credits
๐ License
The MIT License (MIT). See License File for details.
Made with โค๏ธ for the Laravel community