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

v1.2.1 2026-02-02 19:52 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads PHP Version Laravel Version

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, @cannot support
  • ๐Ÿ›ก๏ธ Middleware Protection - role, permission, and role_or_permission middleware
  • ๐ŸŽจ Blade Directives - @role, @hasrole, @hasanyrole, @hasallroles
  • ๐Ÿ“ฆ Type-Safe Enums - IDE-friendly PermissionType and CacheKey enums
  • ๐Ÿฐ 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

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 definitions
  • permissions - Permission definitions
  • permission_role - Role-permission relationships
  • role_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 relationship
  • assignRole($role) - Assign single or multiple roles
  • syncRoles(array $roles, bool $detach = true) - Sync roles
  • syncRolesWithoutDetaching(array $roles) - Sync without detaching
  • revokeRole($role) - Revoke specific role
  • revokeRoles() - Revoke all roles
  • getRoleNames() - Get all role names
  • hasRole($role) - Check single role
  • hasAllRoles(...$roles) - Check all roles
  • hasAnyRole(...$roles) - Check any role

HasPermissions trait provides:

  • permissions() - BelongsToMany relationship
  • givePermissionTo($permission) - Give single or multiple permissions
  • syncPermissions(array $permissions) - Sync permissions
  • revokePermissionTo($permission) - Revoke specific permission
  • revokeAllPermissions() - Revoke all permissions
  • getPermissionNames() - Get all permission names
  • hasPermission($permission) - Check permission (by name or model)
  • hasPermissionTo($permission) - Check if has specific permission
  • getPermissions() - 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 name
  • isProtectedRole() - Check if guarded
  • getPermissionNames() - Get assigned permission names
  • permissions() - BelongsToMany to permissions
  • users() - BelongsToMany to users

Scopes:

  • guarded() - Only guarded roles
  • unguarded() - 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 name
  • getLabel() - Get human-readable label
  • getDescription() - Get description
  • isWildcard() - Check if wildcard pattern
  • getGroup() - Get resource group (e.g., 'users')
  • getType() - Get PermissionType enum
  • roles() - BelongsToMany to roles
  • giveRoleTo($role) - Give role to permission
  • syncRoles(array $roles) - Sync roles
  • revokeRole($role) - Revoke role
  • assignRole($role) - Alias for giveRoleTo

Scopes:

  • wildcard() - Only wildcard permissions
  • byGroup($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

  1. Keep caching enabled in production

  2. Use wildcard permissions to reduce permission count

  3. 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'));
  4. 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

Contributors

๐Ÿ“„ License

The MIT License (MIT). See License File for details.

Made with โค๏ธ for the Laravel community