├── src ├── Exceptions │ ├── RoleAlreadyExists.php │ ├── RoleDoesNotExist.php │ ├── UnauthorizedException.php │ ├── PermissionDoesNotExist.php │ └── PermissionAlreadyExists.php ├── Facades │ └── PermissionManager.php ├── Console │ ├── PermissionSyncRoutesCommand.php │ ├── RoleListCommand.php │ ├── PermissionListCommand.php │ ├── RoleExportCommand.php │ ├── RoleDeleteCommand.php │ ├── UserAssignRoleCommand.php │ ├── UserRevokeRoleCommand.php │ ├── RoleImportCommand.php │ ├── RoleCreateCommand.php │ ├── PermissionDeleteCommand.php │ ├── PermissionCreateCommand.php │ ├── RoleUpdateCommand.php │ ├── RoleAssignPermissionCommand.php │ ├── RoleRevokePermissionCommand.php │ └── InstallCommand.php ├── Handlers │ ├── RoleHandler.php │ └── PermissionHandler.php ├── PermissionManager.php ├── Proxies │ ├── RoleProxy.php │ └── UserProxy.php ├── Models │ ├── Permission.php │ └── Role.php ├── Middleware │ └── CheckPermission.php ├── PermissionManagerServiceProvider.php └── Traits │ └── PermissionTrait.php ├── database └── migrations │ ├── 2025_09_20_000002_create_permissions_table.php │ ├── 2025_09_20_000001_create_roles_table.php │ ├── 2025_09_20_000004_create_user_roles_table.php │ └── 2025_09_20_000003_create_role_permissions_table.php ├── config └── permission-manager.php ├── LICENSE.md ├── composer.json ├── CONTRIBUTING.md └── README.md /src/Exceptions/RoleAlreadyExists.php: -------------------------------------------------------------------------------- 1 | sync(); 17 | $this->info('Routes synced.'); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Console/RoleListCommand.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 18 | $this->info('No roles found.'); 19 | return; 20 | } 21 | $this->table(['ID', 'Slug', 'Name', 'Description'], $roles); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Console/PermissionListCommand.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 18 | $this->info('No permissions found.'); 19 | return; 20 | } 21 | $this->table(['ID', 'Route'], $permissions); 22 | } 23 | } -------------------------------------------------------------------------------- /database/migrations/2025_09_20_000002_create_permissions_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('route')->unique(); 14 | $table->timestamps(); 15 | }); 16 | } 17 | 18 | public function down(): void 19 | { 20 | Schema::dropIfExists(config('permission-manager.tables.permissions')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/Handlers/RoleHandler.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'role' => \HosseinHezami\PermissionManager\Models\Role::class, 8 | 'permission' => \HosseinHezami\PermissionManager\Models\Permission::class, 9 | 'user' => config('auth.providers.users.model'), 10 | ], 11 | 12 | // Tables 13 | 'tables' => [ 14 | 'roles' => 'roles', 15 | 'permissions' => 'permissions', 16 | 'role_permissions' => 'role_permissions', 17 | 'user_roles' => 'user_roles', 18 | ], 19 | 20 | 'cache_duration' => 60, // Cache permissions for 60 minutes 21 | 'log_denials' => false, // Log permission denials 22 | 'wildcards' => true, // Enable wildcard support 23 | 24 | ]; 25 | -------------------------------------------------------------------------------- /database/migrations/2025_09_20_000001_create_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('name'); 14 | $table->string('slug')->unique(); 15 | $table->text('description')->nullable(); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists(config('permission-manager.tables.roles')); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /database/migrations/2025_09_20_000004_create_user_roles_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('user_id'); 13 | $table->unsignedBigInteger('role_id'); 14 | $table->primary(['user_id','role_id']); 15 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 16 | $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down(): void 22 | { 23 | Schema::dropIfExists(config('permission-manager.tables.user_roles')); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/Console/RoleExportCommand.php: -------------------------------------------------------------------------------- 1 | get()->map(function ($role) { 18 | return [ 19 | 'slug' => $role->slug, 20 | 'name' => $role->name, 21 | 'description' => $role->description, 22 | 'permissions' => $role->permissions->pluck('route'), 23 | ]; 24 | }); 25 | 26 | Storage::disk('local')->put($this->argument('file'), $roles->toJson()); 27 | 28 | $this->info('Roles exported to ' . $this->argument('file')); 29 | } 30 | } -------------------------------------------------------------------------------- /database/migrations/2025_09_20_000003_create_role_permissions_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('role_id'); 13 | $table->unsignedBigInteger('permission_id'); 14 | $table->primary(['role_id','permission_id']); 15 | $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); 16 | $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down(): void 22 | { 23 | Schema::dropIfExists(config('permission-manager.tables.role_permissions')); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/Console/RoleDeleteCommand.php: -------------------------------------------------------------------------------- 1 | argument('slug'); 18 | 19 | try { 20 | $role = Role::findBySlug($slug); 21 | $role->delete(); 22 | 23 | $this->info("Role '$slug' deleted successfully."); 24 | 25 | } catch (RoleDoesNotExist $e) { 26 | $this->error($e->getMessage()); 27 | return Command::FAILURE; 28 | } catch (\Exception $e) { 29 | $this->error("Error deleting role: " . $e->getMessage()); 30 | return Command::FAILURE; 31 | } 32 | 33 | return Command::SUCCESS; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Console/UserAssignRoleCommand.php: -------------------------------------------------------------------------------- 1 | argument('user_id'); 17 | $roles = $this->argument('roles'); 18 | 19 | if (count($roles) === 1 && str_contains($roles[0], ',')) { 20 | $roles = explode(',', $roles[0]); 21 | $roles = array_map('trim', $roles); 22 | } 23 | 24 | try { 25 | PermissionManager::user($userId)->assignRole($roles); 26 | 27 | $this->info("Role(s) assigned successfully."); 28 | 29 | } catch (\Exception $e) { 30 | $this->error("Error assigning role: " . $e->getMessage()); 31 | return Command::FAILURE; 32 | } 33 | 34 | return Command::SUCCESS; 35 | } 36 | } -------------------------------------------------------------------------------- /src/Console/UserRevokeRoleCommand.php: -------------------------------------------------------------------------------- 1 | argument('user_id'); 17 | $roles = $this->argument('roles'); 18 | 19 | if (count($roles) === 1 && str_contains($roles[0], ',')) { 20 | $roles = explode(',', $roles[0]); 21 | $roles = array_map('trim', $roles); 22 | } 23 | 24 | try { 25 | PermissionManager::user($userId)->revokeRole($roles); 26 | 27 | $this->info("Role(s) revoked successfully."); 28 | 29 | } catch (\Exception $e) { 30 | $this->error("Error revoking role: " . $e->getMessage()); 31 | return Command::FAILURE; 32 | } 33 | 34 | return Command::SUCCESS; 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hossein Hezami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Console/RoleImportCommand.php: -------------------------------------------------------------------------------- 1 | get($this->argument('file')); 19 | $data = json_decode($json, true); 20 | 21 | foreach ($data as $item) { 22 | $role = Role::updateOrCreate( 23 | ['slug' => $item['slug']], 24 | ['name' => $item['name'], 'description' => $item['description'] ?? null] 25 | ); 26 | $permissions = collect($item['permissions'] ?? [])->map(function ($route) { 27 | return Permission::firstOrCreate(['route' => $route])->id; 28 | }); 29 | $role->permissions()->sync($permissions); 30 | } 31 | 32 | $this->info('Roles imported.'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Console/RoleCreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('slug'); 18 | $name = $this->argument('name'); 19 | $description = $this->argument('description'); 20 | 21 | try { 22 | $role = Role::create([ 23 | 'slug' => $slug, 24 | 'name' => $name, 25 | 'description' => $description, 26 | ]); 27 | 28 | $this->info("Role '$slug' created successfully."); 29 | 30 | } catch (RoleAlreadyExists $e) { 31 | $this->error($e->getMessage()); 32 | return Command::FAILURE; 33 | } catch (\Exception $e) { 34 | $this->error("Error creating role: " . $e->getMessage()); 35 | return Command::FAILURE; 36 | } 37 | 38 | return Command::SUCCESS; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Console/PermissionDeleteCommand.php: -------------------------------------------------------------------------------- 1 | argument('routes'); 18 | 19 | if (count($routes) === 1 && str_contains($routes[0], ',')) { 20 | $routes = explode(',', $routes[0]); 21 | $routes = array_map('trim', $routes); 22 | } 23 | 24 | try { 25 | PermissionManager::permissions()->delete($routes); 26 | 27 | $this->info("Permission(s) deleted successfully."); 28 | 29 | } catch (PermissionDoesNotExist $e) { 30 | $this->error($e->getMessage()); 31 | return Command::FAILURE; 32 | } catch (\Exception $e) { 33 | $this->error("Error deleting permission: " . $e->getMessage()); 34 | return Command::FAILURE; 35 | } 36 | 37 | return Command::SUCCESS; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Console/PermissionCreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('routes'); 18 | 19 | if (count($routes) === 1 && str_contains($routes[0], ',')) { 20 | $routes = explode(',', $routes[0]); 21 | $routes = array_map('trim', $routes); 22 | } 23 | 24 | try { 25 | PermissionManager::permissions()->create($routes); 26 | 27 | $this->info("Permission(s) created successfully."); 28 | 29 | } catch (PermissionAlreadyExists $e) { 30 | $this->error($e->getMessage()); 31 | return Command::FAILURE; 32 | } catch (\Exception $e) { 33 | $this->error("Error creating permission: " . $e->getMessage()); 34 | return Command::FAILURE; 35 | } 36 | 37 | return Command::SUCCESS; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Console/RoleUpdateCommand.php: -------------------------------------------------------------------------------- 1 | argument('slug'); 18 | $name = $this->argument('name'); 19 | $description = $this->argument('description'); 20 | 21 | try { 22 | $role = Role::findBySlug($slug); 23 | if ($name) { 24 | $role->name = $name; 25 | } 26 | if ($description) { 27 | $role->description = $description; 28 | } 29 | $role->save(); 30 | 31 | $this->info("Role '$slug' updated successfully."); 32 | 33 | } catch (RoleDoesNotExist $e) { 34 | $this->error($e->getMessage()); 35 | return Command::FAILURE; 36 | } catch (\Exception $e) { 37 | $this->error("Error updating role: " . $e->getMessage()); 38 | return Command::FAILURE; 39 | } 40 | 41 | return Command::SUCCESS; 42 | } 43 | } -------------------------------------------------------------------------------- /src/Console/RoleAssignPermissionCommand.php: -------------------------------------------------------------------------------- 1 | argument('roleSlug'); 18 | $permissions = $this->argument('permissions'); 19 | 20 | if (count($permissions) === 1 && str_contains($permissions[0], ',')) { 21 | $permissions = explode(',', $permissions[0]); 22 | $permissions = array_map('trim', $permissions); 23 | } 24 | 25 | try { 26 | PermissionManager::role($roleSlug)->assignPermission($permissions); 27 | 28 | $this->info("Permission(s) assigned successfully."); 29 | 30 | } catch (RoleDoesNotExist $e) { 31 | $this->error($e->getMessage()); 32 | return Command::FAILURE; 33 | } catch (\Exception $e) { 34 | $this->error("Error assigning permission: " . $e->getMessage()); 35 | return Command::FAILURE; 36 | } 37 | 38 | return Command::SUCCESS; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Console/RoleRevokePermissionCommand.php: -------------------------------------------------------------------------------- 1 | argument('roleSlug'); 18 | $permissions = $this->argument('permissions'); 19 | 20 | if (count($permissions) === 1 && str_contains($permissions[0], ',')) { 21 | $permissions = explode(',', $permissions[0]); 22 | $permissions = array_map('trim', $permissions); 23 | } 24 | 25 | try { 26 | PermissionManager::role($roleSlug)->revokePermission($permissions); 27 | 28 | $this->info("Permission(s) revoked successfully."); 29 | 30 | } catch (RoleDoesNotExist $e) { 31 | $this->error($e->getMessage()); 32 | return Command::FAILURE; 33 | } catch (\Exception $e) { 34 | $this->error("Error revoking permission: " . $e->getMessage()); 35 | return Command::FAILURE; 36 | } 37 | 38 | return Command::SUCCESS; 39 | } 40 | } -------------------------------------------------------------------------------- /src/PermissionManager.php: -------------------------------------------------------------------------------- 1 | role = Role::findBySlug($slug); 17 | } 18 | 19 | /** 20 | * Assign permission(s). 21 | * 22 | * @param string|array $params Routes 23 | * @return Role 24 | */ 25 | public function assignPermission($params): Role 26 | { 27 | $this->role->assignPermission($params); 28 | return $this->role; 29 | } 30 | 31 | /** 32 | * Revoke permission(s). 33 | * 34 | * @param string|array $params Routes 35 | * @return Role 36 | */ 37 | public function revokePermission($params): Role 38 | { 39 | $this->role->revokePermission($params); 40 | return $this->role; 41 | } 42 | 43 | /** 44 | * Update role. 45 | * 46 | * @param array $params ['name?', 'description?'] 47 | * @return Role 48 | */ 49 | public function update(array $params): Role 50 | { 51 | $this->role->update($params); 52 | return $this->role; 53 | } 54 | 55 | /** 56 | * Delete role. 57 | * 58 | * @return bool 59 | */ 60 | public function delete(): bool 61 | { 62 | return $this->role->delete(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Models/Permission.php: -------------------------------------------------------------------------------- 1 | exists()) { 18 | throw new Exceptions\PermissionAlreadyExists("Permission with route '{$data['route']}' already exists."); 19 | } 20 | return static::query()->create($data); 21 | } 22 | 23 | public function roles(): BelongsToMany 24 | { 25 | return $this->belongsToMany( 26 | Role::class, 27 | config('permission-manager.tables.role_permissions'), 28 | 'permission_id', 29 | 'role_id' 30 | ); 31 | } 32 | 33 | public static function findByRoute(string $route): self 34 | { 35 | return static::where('route', $route)->firstOr(function () use ($route) { 36 | throw new Exceptions\PermissionDoesNotExist("Permission with route '$route' does not exist."); 37 | }); 38 | } 39 | 40 | private static function checkWildcardPermission($route) 41 | { 42 | $permissions = static::all(); 43 | 44 | foreach ($permissions as $permission) { 45 | if (Str::is($permission->route, $route)) { 46 | return $permission; 47 | } 48 | } 49 | 50 | return null; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Handlers/PermissionHandler.php: -------------------------------------------------------------------------------- 1 | $route]); 35 | } 36 | } 37 | 38 | /** 39 | * Delete permission(s). 40 | * 41 | * @param string|array $routes 42 | * @return void 43 | */ 44 | public function delete($routes): void 45 | { 46 | $routes = is_array($routes) ? $routes : [$routes]; 47 | Permission::whereIn('route', $routes)->delete(); 48 | } 49 | 50 | /** 51 | * Sync routes to permissions. 52 | * 53 | * @return void 54 | */ 55 | public function sync(): void 56 | { 57 | $routes = RouteFacade::getRoutes(); 58 | foreach ($routes as $route) { 59 | $name = $route->getName(); 60 | if ($name && !Permission::where('route', $name)->exists()) { 61 | Permission::create(['route' => $name]); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hosseinhezami/laravel-permission-manager", 3 | "description": "Advanced permission manager for Laravel.", 4 | "keywords": [ 5 | "permission", 6 | "roles", 7 | "laravel", 8 | "authorization", 9 | "acl", 10 | "access-control", 11 | "permission-management", 12 | "role-management", 13 | "laravel-permissions", 14 | "laravel-roles", 15 | "user-permissions", 16 | "multi-role", 17 | "permission-system", 18 | "rbac", 19 | "role-based-access-control", 20 | "permission-manager", 21 | "laravel-package", 22 | "middleware", 23 | "blade-directives", 24 | "permission-check", 25 | "user-roles", 26 | "access-management", 27 | "security", 28 | "authentication", 29 | "authorization-system" 30 | ], 31 | "version": "1.0.1", 32 | "type": "library", 33 | "license": "MIT", 34 | "authors": [ 35 | { 36 | "name": "Hossein Hezami", 37 | "email": "hossein.hezami@gmail.com" 38 | } 39 | ], 40 | "require": { 41 | "php": "^8.2|^8.3|^8.4|^8.5", 42 | "illuminate/support": "^10.0|^11.0|^12.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "HosseinHezami\\PermissionManager\\": "src/" 47 | } 48 | }, 49 | "extra": { 50 | "laravel": { 51 | "providers": [ 52 | "HosseinHezami\\PermissionManager\\PermissionManagerServiceProvider" 53 | ], 54 | "aliases": { 55 | "PermissionManager": "HosseinHezami\\PermissionManager\\Facades\\PermissionManager" 56 | } 57 | } 58 | }, 59 | "scripts": { 60 | "post-autoload-dump": [ 61 | "@php artisan vendor:publish --tag=config --force", 62 | "@php artisan vendor:publish --tag=migrate --force" 63 | ] 64 | }, 65 | "minimum-stability": "stable", 66 | "prefer-stable": true 67 | } -------------------------------------------------------------------------------- /src/Proxies/UserProxy.php: -------------------------------------------------------------------------------- 1 | user = $userModel::findOrFail($userId); 20 | } 21 | 22 | /** 23 | * Assign role(s). 24 | * 25 | * @param string|array $params Slugs 26 | * @return mixed User 27 | */ 28 | public function assignRole($params) 29 | { 30 | return $this->user->assignRole($params); 31 | } 32 | 33 | /** 34 | * Revoke role(s). 35 | * 36 | * @param string|array $params Slugs 37 | * @return mixed User 38 | */ 39 | public function revokeRole($params) 40 | { 41 | return $this->user->revokeRole($params); 42 | } 43 | 44 | /** 45 | * Get user's roles. 46 | * 47 | * @return Collection 48 | */ 49 | public function roles(): Collection 50 | { 51 | return $this->user->roles; 52 | } 53 | 54 | /** 55 | * Get user's permissions. 56 | * 57 | * @return Collection 58 | */ 59 | public function permissions(): Collection 60 | { 61 | return $this->user->permissions(); 62 | } 63 | 64 | /** 65 | * Check if user has role. 66 | * 67 | * @param string|array $params Slug(s) 68 | * @return bool 69 | */ 70 | public function hasRole($params): bool 71 | { 72 | return $this->user->hasRole($params); 73 | } 74 | 75 | /** 76 | * Check if user has permission. 77 | * 78 | * @param string $params Route 79 | * @return bool 80 | */ 81 | public function hasPermission($params): bool 82 | { 83 | return $this->user->hasPermissionTo($params); 84 | } 85 | } -------------------------------------------------------------------------------- /src/Middleware/CheckPermission.php: -------------------------------------------------------------------------------- 1 | user(); 13 | if (!$user) { 14 | return response()->json(['message' => 'Unauthenticated'], 401); 15 | } 16 | 17 | if (str_contains($data, 'role:')) { 18 | $inputData = str_replace('role:', '', $data); 19 | if (str_contains($inputData, '|')) { 20 | $inputData = explode('|', $inputData); 21 | $inputData = array_map('trim', $inputData); 22 | } 23 | $inputData = is_array($inputData) ? $inputData : [$inputData]; 24 | foreach ($inputData as $role) { 25 | if ($user->hasRole($role)) { 26 | return $next($request); 27 | } 28 | } 29 | } 30 | 31 | if (str_contains($data, 'permission:')) { 32 | $inputData = str_replace('permission:', '', $data); 33 | if (str_contains($inputData, '|')) { 34 | $inputData = explode('|', $inputData); 35 | $inputData = array_map('trim', $inputData); 36 | } 37 | $inputData = is_array($inputData) ? $inputData : [$inputData]; 38 | foreach ($inputData as $permission) { 39 | if ($user->hasPermissionTo($permission)) { 40 | return $next($request); 41 | } 42 | } 43 | } 44 | 45 | $routeName = optional($request->route())->getName(); 46 | if ($routeName) { 47 | if ($user->hasPermissionTo($routeName)) { 48 | return $next($request); 49 | } 50 | } 51 | 52 | if (config('permission-manager.log_denials')) { 53 | logger()->warning('Permission denied', ['User ID' => $user->id, 'Path' => $request->path(), 'Route' => $routeName]); 54 | } 55 | 56 | if ($request->expectsJson()) { 57 | return response()->json(['error' => 'Unauthorized'], 403); 58 | } 59 | 60 | abort(403); 61 | } 62 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Laravel Permission Manager 2 | 3 | Thank you for considering contributing to **Laravel Permission Manager**! 4 | We welcome all kinds of contributions, including bug reports, feature requests, documentation improvements, and code contributions. 5 | 6 | --- 7 | 8 | ## How to Contribute 9 | 10 | ### 1. Fork the Repository 11 | - Click the **Fork** button on the top right of the repository page. 12 | - Clone your fork locally: 13 | ```bash 14 | git clone https://github.com/hosseinhezami/laravel-permission-manager.git 15 | cd laravel-permission-manager 16 | ```` 17 | 18 | ### 2. Create a Feature Branch 19 | 20 | * Always work on a new branch: 21 | 22 | ```bash 23 | git checkout -b feature/your-feature-name 24 | ``` 25 | * Use descriptive branch names such as: 26 | 27 | * `feature/add-wildcard-permission` 28 | * `fix/role-sync-bug` 29 | * `docs/update-readme` 30 | 31 | ### 3. Install Dependencies 32 | 33 | * Install required dependencies using Composer: 34 | 35 | ```bash 36 | composer install 37 | ``` 38 | 39 | ### 4. Run Tests 40 | 41 | * Make sure all tests pass before submitting your changes: 42 | 43 | ```bash 44 | php artisan test 45 | ``` 46 | * If you add new functionality, write corresponding tests. 47 | 48 | ### 5. Commit Guidelines 49 | 50 | * Follow conventional commits where possible: 51 | 52 | * `feat: add new role export feature` 53 | * `fix: resolve caching issue in permission sync` 54 | * `docs: update installation guide` 55 | 56 | ### 6. Push Changes 57 | 58 | * Push your branch to your fork: 59 | 60 | ```bash 61 | git push origin feature/your-feature-name 62 | ``` 63 | 64 | ### 7. Submit a Pull Request 65 | 66 | * Open a PR against the `main` branch. 67 | * Clearly describe: 68 | 69 | * The purpose of your changes. 70 | * Any issues it fixes (e.g., `Fixes #12`). 71 | * Additional notes for reviewers. 72 | 73 | --- 74 | 75 | ## Code Style 76 | 77 | * Follow **PSR-12** coding standards. 78 | * Use **PHPStan** or **Laravel Pint** for static analysis and code style fixes. 79 | * Keep functions small and focused. 80 | * Write meaningful docblocks for public methods. 81 | 82 | --- 83 | 84 | ## Reporting Issues 85 | 86 | If you find a bug, please open an issue with: 87 | 88 | * A clear title. 89 | * Steps to reproduce the bug. 90 | * Expected vs actual behavior. 91 | * Laravel version and PHP version. 92 | 93 | --- 94 | 95 | ## Community Guidelines 96 | 97 | * Be respectful and collaborative. 98 | * Avoid duplicate issues/PRs—search before creating new ones. 99 | * Keep discussions constructive and focused on improving the project. 100 | 101 | --- 102 | 103 | ## License 104 | 105 | By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE.md). 106 | -------------------------------------------------------------------------------- /src/PermissionManagerServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../config/permission-manager.php', 'permission-manager'); 22 | 23 | // Bind the PermissionManager 24 | $this->app->singleton(PermissionManager::class, function () { 25 | return new PermissionManager(); 26 | }); 27 | } 28 | 29 | public function boot() 30 | { 31 | // Publishables 32 | if ($this->app->runningInConsole()) { 33 | $this->publishes([ 34 | __DIR__.'/../config/permission-manager.php' => config_path('permission-manager.php'), 35 | ], 'config'); 36 | 37 | $this->publishes([ 38 | __DIR__.'/../database/migrations/' => database_path('migrations'), 39 | ], 'migrations'); 40 | 41 | // Register commands 42 | $this->commands([ 43 | Console\InstallCommand::class, 44 | Console\RoleListCommand::class, 45 | Console\RoleCreateCommand::class, 46 | Console\RoleUpdateCommand::class, 47 | Console\RoleDeleteCommand::class, 48 | Console\PermissionListCommand::class, 49 | Console\PermissionCreateCommand::class, 50 | Console\PermissionDeleteCommand::class, 51 | Console\PermissionSyncRoutesCommand::class, 52 | Console\RoleAssignPermissionCommand::class, 53 | Console\RoleRevokePermissionCommand::class, 54 | Console\RoleImportCommand::class, 55 | Console\RoleExportCommand::class, 56 | Console\UserAssignRoleCommand::class, 57 | Console\UserRevokeRoleCommand::class, 58 | ]); 59 | } 60 | 61 | // Blade directives 62 | Blade::directive('hasRole', function ($role) { 63 | return "check() && auth()->user()->hasRole({$role})): ?>"; 64 | }); 65 | Blade::directive('endhasRole', function () { 66 | return ''; 67 | }); 68 | Blade::directive('hasPermission', function ($permission) { 69 | return "check() && auth()->user()->hasPermissionTo({$permission})): ?>"; 70 | }); 71 | Blade::directive('endhasPermission', function () { 72 | return ''; 73 | }); 74 | 75 | // Register Gates 76 | Gate::define('has-permission', function ($user, $permission) { 77 | return $user->hasPermissionTo($permission); 78 | }); 79 | 80 | // Register middleware 81 | Route::aliasMiddleware('pm', CheckPermission::class); 82 | 83 | // Load migrations 84 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 85 | } 86 | } -------------------------------------------------------------------------------- /src/Models/Role.php: -------------------------------------------------------------------------------- 1 | belongsToMany( 20 | Permission::class, 21 | config('permission-manager.tables.role_permissions'), 22 | 'role_id', 23 | 'permission_id' 24 | ); 25 | } 26 | 27 | public function users(): BelongsToMany 28 | { 29 | return $this->belongsToMany( 30 | config('permission-manager.models.user'), 31 | config('permission-manager.tables.user_roles'), 32 | 'role_id', 33 | 'user_id' 34 | ); 35 | } 36 | 37 | public function assignPermission($routes) 38 | { 39 | $routes = is_array($routes) ? $routes : [$routes]; 40 | $permissions = Permission::whereIn('route', $routes)->pluck('id'); 41 | $this->permissions()->syncWithoutDetaching($permissions); 42 | $this->forgetCachedPermissions(); 43 | return $this; 44 | } 45 | 46 | public function revokePermission($routes) 47 | { 48 | $routes = is_array($routes) ? $routes : [$routes]; 49 | $permissions = Permission::whereIn('route', $routes)->pluck('id'); 50 | $this->permissions()->detach($permissions); 51 | $this->forgetCachedPermissions(); 52 | return $this; 53 | } 54 | 55 | public function hasPermissionTo(string $route): bool 56 | { 57 | $permissions = $this->getAllPermissions(); 58 | 59 | if (!config('permission-manager.wildcards')) { 60 | return $permissions->contains($route); 61 | } 62 | 63 | foreach ($permissions as $pattern) { 64 | if ($this->matchesPattern($pattern, $route)) { 65 | return true; 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | 72 | public function getAllPermissions(): Collection 73 | { 74 | return Cache::remember( 75 | 'permission_manager.role_permissions.' . $this->id, 76 | config('permission-manager.cache_duration') * 60, 77 | function () { 78 | return $this->permissions->pluck('route'); 79 | } 80 | ); 81 | } 82 | 83 | public function forgetCachedPermissions(): void 84 | { 85 | Cache::forget('permission_manager.role_permissions.' . $this->id); 86 | } 87 | 88 | protected function matchesPattern(string $pattern, string $route): bool 89 | { 90 | if ($pattern === $route) { 91 | return true; 92 | } 93 | 94 | $regex = str_replace(['.', '*'], ['\.', '.*'], $pattern); 95 | return (bool) preg_match('/^' . $regex . '$/', $route); 96 | } 97 | 98 | public static function create(array $data): self 99 | { 100 | if (static::where('slug', $data['slug'])->exists()) { 101 | throw new Exceptions\RoleAlreadyExists("Role with slug '{$data['slug']}' already exists."); 102 | } 103 | 104 | return static::query()->create($data); 105 | } 106 | 107 | public static function findBySlug(string $slug): self 108 | { 109 | return static::where('slug', $slug)->firstOr(function () use ($slug) { 110 | throw new Exceptions\RoleDoesNotExist("Role with slug '$slug' does not exist."); 111 | }); 112 | } 113 | } -------------------------------------------------------------------------------- /src/Console/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Installing Permission Manager..'); 20 | 21 | // Publish the configuration file 22 | $this->publishConfiguration(); 23 | 24 | // Publish and run migrations 25 | $this->publishAndRunMigrations(); 26 | 27 | // Ask to add trait to User model 28 | $this->addTraitToUserModel(); 29 | 30 | $this->info('Permission Manager installed successfully.'); 31 | } 32 | 33 | protected function publishConfiguration() 34 | { 35 | if (!file_exists(config_path('permission-manager.php'))) { 36 | $this->call('vendor:publish', [ 37 | '--provider' => 'HosseinHezami\PermissionManager\PermissionManagerServiceProvider', 38 | '--tag' => 'config' 39 | ]); 40 | $this->info('Configuration file published.'); 41 | } else { 42 | if ($this->confirm('Configuration file already exists. Overwrite?', false)) { 43 | $this->call('vendor:publish', [ 44 | '--provider' => 'HosseinHezami\PermissionManager\PermissionManagerServiceProvider', 45 | '--tag' => 'config', 46 | '--force' => true 47 | ]); 48 | $this->info('Configuration file overwritten.'); 49 | } else { 50 | $this->info('Configuration file skipped.'); 51 | } 52 | } 53 | } 54 | 55 | protected function publishAndRunMigrations() 56 | { 57 | // Check if migrations have already been published 58 | $migrationFiles = [ 59 | '2025_09_20_000001_create_roles_table', 60 | '2025_09_20_000002_create_permissions_table.php', 61 | '2025_09_20_000003_create_role_permissions_table.php', 62 | '2025_09_20_000004_create_user_roles_table.php' 63 | ]; 64 | 65 | $migrationsExist = false; 66 | foreach ($migrationFiles as $migrationFile) { 67 | if (file_exists(database_path('migrations/' . $migrationFile))) { 68 | $migrationsExist = true; 69 | break; 70 | } 71 | } 72 | 73 | if (!$migrationsExist) { 74 | $this->call('vendor:publish', [ 75 | '--provider' => 'HosseinHezami\PermissionManager\PermissionManagerServiceProvider', 76 | '--tag' => 'migrations' 77 | ]); 78 | $this->info('Migration files published.'); 79 | } else { 80 | $this->info('Migration files already exist.'); 81 | } 82 | 83 | // Run migrations 84 | if ($this->option('migrate') || $this->confirm('Do you want to run the migrations now?', true)) { 85 | $this->call('migrate', [ 86 | '--force' => $this->option('force') 87 | ]); 88 | $this->info('Migrations executed successfully.'); 89 | } else { 90 | $this->info('Migrations skipped. You can run them later with: php artisan migrate'); 91 | } 92 | } 93 | 94 | protected function addTraitToUserModel() 95 | { 96 | if ($this->confirm('Do you want to add the PermissionTrait to your User model?', true)) { 97 | $userModelPath = $this->option('user-model') ?: app_path('Models/User.php'); 98 | 99 | if (!File::exists($userModelPath)) { 100 | // Try alternative paths 101 | $userModelPath = app_path('User.php'); 102 | 103 | if (!File::exists($userModelPath)) { 104 | $this->error("User model not found at: {$userModelPath}"); 105 | $this->info('Please manually add the trait to your User model:'); 106 | $this->info('use HosseinHezami\PermissionManager\Traits\PermissionTrait;'); 107 | return; 108 | } 109 | } 110 | 111 | $this->addTraitToFile($userModelPath); 112 | } 113 | } 114 | 115 | protected function addTraitToFile(string $filePath) 116 | { 117 | $content = File::get($filePath); 118 | 119 | // Check if trait already exists 120 | if (str_contains($content, 'use PermissionTrait;') || 121 | str_contains($content, 'use HosseinHezami\PermissionManager\Traits\PermissionTrait;')) { 122 | $this->info('Trait already added to User model.'); 123 | return; 124 | } 125 | 126 | // Add the use statement for the trait 127 | $useTrait = 'use HosseinHezami\PermissionManager\Traits\PermissionTrait;'; 128 | 129 | // Add use statement after the last use statement 130 | if (preg_match('/^(use .*;)$/m', $content)) { 131 | $content = preg_replace('/^(use .*;)$/m', "$1\n{$useTrait}", $content, 1); 132 | } else { 133 | // Add after namespace if no use statements found 134 | $content = preg_replace( 135 | '/^(namespace App\\\Models;|namespace App;)\s*/m', 136 | "$1\n\n{$useTrait}\n", 137 | $content 138 | ); 139 | } 140 | 141 | // Add the use statement for the trait 142 | // $useTrait = 'use HosseinHezami\PermissionManager\Traits\PermissionTrait;'; 143 | // $content = preg_replace('/^(use .*;)$/m', "$1\n{$useTrait}", $content, 1); 144 | 145 | // Add the trait to the class 146 | if (str_contains($content, 'use HasFactory, Notifiable;')) { 147 | $content = str_replace( 148 | 'use HasFactory, Notifiable;', 149 | 'use HasFactory, Notifiable, PermissionTrait;', 150 | $content 151 | ); 152 | } elseif (str_contains($content, 'use Notifiable;')) { 153 | $content = str_replace( 154 | 'use Notifiable;', 155 | 'use Notifiable, PermissionTrait;', 156 | $content 157 | ); 158 | } elseif (str_contains($content, 'use HasFactory;')) { 159 | $content = str_replace( 160 | 'use HasFactory;', 161 | 'use HasFactory, PermissionTrait;', 162 | $content 163 | ); 164 | } else { 165 | // If no common trait pattern is found, add the trait after the class opening 166 | if (preg_match('/(class User.*?\{)/s', $content, $matches)) { 167 | $content = str_replace( 168 | $matches[1], 169 | $matches[1] . "\n use PermissionTrait;", 170 | $content 171 | ); 172 | } 173 | } 174 | 175 | File::put($filePath, $content); 176 | $this->info('Trait added to User model successfully.'); 177 | } 178 | } -------------------------------------------------------------------------------- /src/Traits/PermissionTrait.php: -------------------------------------------------------------------------------- 1 | belongsToMany( 24 | config('permission-manager.models.role'), 25 | config('permission-manager.tables.user_roles'), 26 | 'user_id', 27 | 'role_id' 28 | ); 29 | } 30 | 31 | /** 32 | * Assign role(s) to user. 33 | * 34 | * @param string|array $roles Slugs 35 | * @return $this 36 | */ 37 | public function assignRole($roles) 38 | { 39 | $roles = is_array($roles) ? $roles : [$roles]; 40 | $roleIds = Role::whereIn('slug', $roles)->pluck('id'); 41 | $this->roles()->syncWithoutDetaching($roleIds); 42 | $this->forgetCachedPermissions(); 43 | return $this; 44 | } 45 | 46 | /** 47 | * Revoke role(s) from user. 48 | * 49 | * @param string|array $roles Slugs 50 | * @return $this 51 | */ 52 | public function revokeRole($roles) 53 | { 54 | $roles = is_array($roles) ? $roles : [$roles]; 55 | $roleIds = Role::whereIn('slug', $roles)->pluck('id'); 56 | $this->roles()->detach($roleIds); 57 | $this->forgetCachedPermissions(); 58 | return $this; 59 | } 60 | 61 | /** 62 | * Check if user has role(s). 63 | * 64 | * @param string|array $role Slug(s) 65 | * @return bool 66 | */ 67 | public function hasRole($role): bool 68 | { 69 | $roles = is_array($role) ? $role : [$role]; 70 | return $this->roles()->whereIn('slug', $roles)->exists(); 71 | } 72 | 73 | /** 74 | * Check if the user has the specified permission(s) 75 | * 76 | * This method checks if the user has the given permission(s). It supports both 77 | * single string permissions and arrays of permissions. When checking multiple 78 | * permissions, you can specify whether all permissions are required or just one. 79 | * 80 | * @param string|array $permissions The permission(s) to check. Can be a single 81 | * permission string or an array of permissions. 82 | * @param bool $requireAll Whether all permissions must be present when checking 83 | * multiple permissions. Ignored for single string permissions. 84 | * - true: All permissions must be present (AND logic) 85 | * - false: At least one permission must be present (OR logic) 86 | * 87 | * @return bool 88 | * - For single permission: true if the user has the permission, false otherwise 89 | * - For multiple permissions with $requireAll=true: true only if user has ALL permissions 90 | * - For multiple permissions with $requireAll=false: true if user has AT LEAST ONE permission 91 | */ 92 | public function hasPermissionTo(string|array $permissions, bool $requireAll = true): bool 93 | { 94 | // If input is a single string, $requireAll parameter is ignored 95 | if (is_string($permissions)) { 96 | $userPermissions = $this->getAllPermissions(); 97 | 98 | // If wildcards are disabled, perform simple contains check 99 | if (!config('permission-manager.wildcards')) { 100 | return $userPermissions->contains($permissions); 101 | } 102 | 103 | // If wildcards are enabled, check each pattern for matches 104 | foreach ($userPermissions as $pattern) { 105 | if ($this->matchesPattern($pattern, $permissions)) { 106 | return true; 107 | } 108 | } 109 | 110 | return false; 111 | } 112 | 113 | // If input is an array, process multiple permissions 114 | $permissions = (array) $permissions; 115 | 116 | // Return false for empty permission arrays 117 | if (empty($permissions)) { 118 | return false; 119 | } 120 | 121 | $userPermissions = $this->getAllPermissions(); 122 | $matchedCount = 0; 123 | 124 | // Check each permission in the array 125 | foreach ($permissions as $permission) { 126 | $found = false; 127 | 128 | if (!config('permission-manager.wildcards')) { 129 | // Simple check without wildcard support 130 | $found = $userPermissions->contains($permission); 131 | } else { 132 | // Check with wildcard support 133 | foreach ($userPermissions as $pattern) { 134 | if ($this->matchesPattern($pattern, $permission)) { 135 | $found = true; 136 | break; 137 | } 138 | } 139 | } 140 | 141 | if ($found) { 142 | $matchedCount++; 143 | 144 | // If requireAll=false and at least one permission is found, return true immediately 145 | if (!$requireAll) { 146 | return true; 147 | } 148 | } else { 149 | // If requireAll=true and any permission is missing, return false immediately 150 | if ($requireAll) { 151 | return false; 152 | } 153 | } 154 | } 155 | 156 | // If we reach this point: 157 | // - If requireAll=true: all permissions were found (return true) 158 | // - If requireAll=false: no permissions were found (return false) 159 | return $requireAll; 160 | } 161 | 162 | /** 163 | * Get all permissions (routes) from user's roles. 164 | * 165 | * @return Collection 166 | */ 167 | public function permissions(): Collection 168 | { 169 | return $this->getAllPermissions(); 170 | } 171 | 172 | /** 173 | * Get cached permissions. 174 | * 175 | * @return Collection 176 | */ 177 | protected function getAllPermissions(): Collection 178 | { 179 | return Cache::remember( 180 | 'permission_manager.user_permissions.' . $this->id, 181 | config('permission-manager.cache_duration') * 60, 182 | function () { 183 | return $this->roles->flatMap(function ($role) { 184 | return $role->permissions->pluck('route'); 185 | })->unique(); 186 | } 187 | ); 188 | } 189 | 190 | /** 191 | * Forget cached permissions. 192 | * 193 | * @return void 194 | */ 195 | public function forgetCachedPermissions(): void 196 | { 197 | Cache::forget('permission_manager.user_permissions.' . $this->id); 198 | } 199 | 200 | /** 201 | * Match route against pattern with wildcard support. 202 | * 203 | * @param string $pattern 204 | * @param string $route 205 | * @return bool 206 | */ 207 | protected function matchesPattern(string $pattern, string $route): bool 208 | { 209 | if ($pattern === $route) { 210 | return true; 211 | } 212 | 213 | // Convert pattern to regex: * -> .*, . -> \. 214 | $regex = str_replace(['.', '*'], ['\.', '.*'], $pattern); 215 | return (bool) preg_match('/^' . $regex . '$/', $route); 216 | } 217 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Permission Manager 2 | 3 | A comprehensive and advanced permission management system for Laravel applications. This package provides a robust role-based access control (RBAC) system with support for wildcard permissions, blade directives, Artisan commands, and a fluent API. 4 | 5 | [![Version](https://img.shields.io/packagist/v/hosseinhezami/laravel-permission-manager.svg)](https://packagist.org/packages/hosseinhezami/laravel-permission-manager) 6 | [![Downloads](https://img.shields.io/packagist/dt/hosseinhezami/laravel-permission-manager.svg)](https://packagist.org/packages/hosseinhezami/laravel-permission-manager) 7 | [![Star](https://img.shields.io/packagist/stars/hosseinhezami/laravel-permission-manager.svg)](https://packagist.org/packages/hosseinhezami/laravel-permission-manager) 8 | [![License](https://img.shields.io/packagist/l/hosseinhezami/laravel-permission-manager.svg)](https://packagist.org/packages/hosseinhezami/laravel-permission-manager) 9 | [![Laravel Compatible](https://img.shields.io/badge/Laravel-10%2B-brightgreen.svg)](https://hosseinhezami.github.io/laravel-permission-manager) 10 | 11 | ## Features 12 | 13 | - **Role-Based Access Control (RBAC)**: Assign multiple roles to users, and permissions to roles. 14 | - **Multiple Roles**: Support for multiple roles per user. 15 | - **Route-Based Permissions**: Permissions are tied to Laravel route names (e.g., `users`). 16 | - **Wildcard Support**: Use wildcards for permissions (e.g., `admin.*`, `admin*`, `*admin`, `*admin*`) for flexible route matching. 17 | - **Blade Directives**: Easily check roles and permissions in Blade templates (`@hasRole`, `@hasPermission`). 18 | - **Facade Methods**: Conveniently manage roles, permissions, and users via the `PermissionManager` facade. 19 | - **Console Commands**: Comprehensive Artisan commands for managing roles and permissions. 20 | - **Caching**: Permissions are cached for performance. 21 | - **Exceptions**: Custom exceptions for robust error handling. 22 | - **Multi-Guard Support**: Works with Laravel's authentication guards. 23 | - **Export/Import**: Export roles and permissions to JSON and import them back. 24 | - **Middleware**: Protect routes with role or permission checks (`pm`, `pm:role:admin|editor`). 25 | - **User Trait**: Adds role and permission methods to the User model. 26 | - **Easy Installation**: Auto-discovery for easy installation. 27 | 28 | ## Installation 29 | 30 | 1. Install the package via Composer: 31 | 32 | ```bash 33 | composer require HosseinHezami/laravel-permission-manager 34 | ``` 35 | 36 | 2. (Optional) If Laravel's package auto-discovery is disabled, add the service provider and facade to `config/app.php`: 37 | 38 | ```php 39 | 'providers' => [ 40 | // ... 41 | HosseinHezami\PermissionManager\PermissionManagerServiceProvider::class, 42 | ], 43 | 'aliases' => [ 44 | // ... 45 | 'PermissionManager' => HosseinHezami\PermissionManager\Facades\PermissionManager::class, 46 | ], 47 | ``` 48 | 49 | 3. Publish the configuration and migrations: 50 | 51 | ```bash 52 | php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="config" 53 | php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="migrations" 54 | ``` 55 | 56 | 4. Run the migrations to create the necessary tables: 57 | 58 | ```bash 59 | php artisan migrate 60 | ``` 61 | 62 | 5. Add the `PermissionTrait` trait to your `User` model: 63 | 64 | ```php 65 | namespace App\Models; 66 | 67 | use HosseinHezami\PermissionManager\Traits\PermissionTrait; 68 | use Illuminate\Foundation\Auth\User as Authenticatable; 69 | 70 | class User extends Authenticatable 71 | { 72 | use PermissionTrait; 73 | } 74 | ``` 75 | 76 | To run the install command which sets up the configuration and optionally adds the trait to your User model: 77 | 78 | ```bash 79 | # Install the permission manager package with default settings 80 | php artisan permission-manager:install 81 | ``` 82 | ```bash 83 | # Install and automatically run the migrations for permission tables 84 | php artisan permission-manager:install --migrate 85 | ``` 86 | ```bash 87 | # Install, run migrations and force overwrite existing files if any 88 | php artisan permission-manager:install --migrate --force 89 | ``` 90 | ```bash 91 | # Install with custom User model path to automatically add the PermissionTrait trait 92 | php artisan permission-manager:install --user-model=/path/to/User.php 93 | ``` 94 | 95 | ### Configuration 96 | 97 | After publishing the configuration file, you can customize the package behavior in `config/permission-manager.php`: 98 | 99 | ```php 100 | return [ 101 | 'models' => [ 102 | 'role' => \HosseinHezami\PermissionManager\Models\Role::class, 103 | 'permission' => \HosseinHezami\PermissionManager\Models\Permission::class, 104 | 'user' => \App\Models\User::class, 105 | ], 106 | 'tables' => [ 107 | 'roles' => 'roles', 108 | 'permissions' => 'permissions', 109 | 'role_permissions' => 'role_permissions', 110 | 'user_roles' => 'user_roles', 111 | ], 112 | 'cache_duration' => 60, // Cache permissions for 60 minutes 113 | 'log_denials' => false, // Log permission/role denials 114 | 'wildcards' => true, // Enable wildcard support 115 | ]; 116 | ``` 117 | 118 | ## Database Migrations 119 | 120 | The package includes the following database migrations: 121 | 122 | ```php 123 | Schema::create('roles', function (Blueprint $table) { 124 | $table->id(); 125 | $table->string('name'); 126 | $table->string('slug')->unique(); 127 | $table->text('description')->nullable(); 128 | $table->timestamps(); 129 | }); 130 | 131 | Schema::create('permissions', function (Blueprint $table) { 132 | $table->id(); 133 | $table->string('route')->unique(); 134 | $table->timestamps(); 135 | }); 136 | 137 | Schema::create('role_permissions', function (Blueprint $table) { 138 | $table->unsignedBigInteger('role_id'); 139 | $table->unsignedBigInteger('permission_id'); 140 | $table->primary(['role_id','permission_id']); 141 | $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); 142 | $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade'); 143 | $table->timestamps(); 144 | }); 145 | 146 | Schema::create('user_roles', function (Blueprint $table) { 147 | $table->unsignedBigInteger('user_id'); 148 | $table->unsignedBigInteger('role_id'); 149 | $table->primary(['user_id','role_id']); 150 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 151 | $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); 152 | $table->timestamps(); 153 | }); 154 | ``` 155 | 156 | ## Usage 157 | 158 | ### Blade Directives 159 | 160 | Check for roles in your Blade templates: 161 | 162 | ```blade 163 | 164 | @hasRole('admin') 165 | 166 | @endhasRole 167 | 168 | @hasPermission('users.edit') 169 | 170 | @endhasPermission 171 | ``` 172 | 173 | ### Middleware 174 | 175 | Protect your routes with middleware: 176 | 177 | ```php 178 | // ->middleware('pm'); 179 | Route::get('/admin', [AdminController::class, 'index']) 180 | ->middleware('pm:role:admin|manager'); 181 | Route::get('/posts', [PostController::class, 'create']) 182 | ->middleware('pm:permission:posts|post.create'); 183 | ``` 184 | 185 | ### Artisan Commands 186 | 187 | The package provides several Artisan commands for managing roles and permissions: 188 | 189 | ```bash 190 | # List all roles 191 | php artisan roles:list 192 | 193 | # List all permissions 194 | php artisan permissions:list 195 | 196 | # Create a new role 197 | php artisan role:create admin "Administrator" "Has full system access" 198 | 199 | # Update a role 200 | php artisan role:update admin --name="Super Admin" --description="Updated description" 201 | 202 | # Delete a role 203 | php artisan role:delete admin 204 | 205 | # Create permissions 206 | php artisan permission:create "users.*" 207 | php artisan permission:create "users.create,users.edit" 208 | 209 | # Delete permissions 210 | php artisan permission:delete "users.edit" 211 | php artisan permission:delete "users.create,users.edit" 212 | 213 | # Sync routes with permissions table 214 | php artisan permission:sync-routes 215 | 216 | # Assign permissions to a role 217 | php artisan role:assign-permission admin "users.*" 218 | php artisan role:assign-permission admin "users.create,users.edit" 219 | 220 | # Revoke permissions from a role 221 | php artisan role:revoke-permission admin "users.edit" 222 | 223 | # Export roles to a JSON file 224 | php artisan role:export roles.json 225 | 226 | # Import roles from a JSON file 227 | php artisan role:import roles.json 228 | 229 | # Assign roles to a user 230 | php artisan user:assign-role 1 admin 231 | php artisan user:assign-role 1 "admin,editor" 232 | 233 | # Revoke roles from a user 234 | php artisan user:revoke-role 1 admin 235 | ``` 236 | 237 | ### Facade Methods 238 | 239 | Use the facade for programmatic management: 240 | 241 | ```php 242 | use HosseinHezami\PermissionManager\Facades\PermissionManager; 243 | 244 | // Get all roles 245 | $roles = PermissionManager::roles()->list(); 246 | 247 | // Get all permissions 248 | $permissions = PermissionManager::permissions()->list(); 249 | 250 | // Sync routes with permissions 251 | PermissionManager::permissions()->sync(); 252 | 253 | // Create permissions 254 | PermissionManager::permissions()->create('users.create'); 255 | PermissionManager::permissions()->create(['users.create', 'users.edit']); 256 | 257 | // Delete permissions 258 | PermissionManager::permissions()->delete('users.create'); 259 | PermissionManager::permissions()->delete(['users.create', 'users.edit']); 260 | 261 | // Create a role 262 | PermissionManager::roles()->create([ 263 | 'slug' => 'admin', 264 | 'name' => 'Administrator', 265 | 'description' => 'Has full system access' 266 | ]); 267 | 268 | // Role operations 269 | PermissionManager::role('admin')->assignPermission('users.edit'); 270 | PermissionManager::role('admin')->revokePermission('users.edit'); 271 | PermissionManager::role('admin')->update(['name' => 'Super Admin']); 272 | PermissionManager::role('admin')->delete(); 273 | 274 | // User operations 275 | PermissionManager::user($userId)->assignRole(['admin', 'editor']); 276 | PermissionManager::user($userId)->revokeRole('admin'); 277 | $roles = PermissionManager::user($userId)->roles(); 278 | $permissions = PermissionManager::user($userId)->permissions(); 279 | $hasRole = PermissionManager::user($userId)->hasRole('admin'); 280 | $hasPermission = PermissionManager::user($userId)->hasPermission('users.edit'); 281 | ``` 282 | 283 | ### Wildcard Permissions 284 | 285 | The package supports wildcard permissions for flexible route matching: 286 | 287 | ```php 288 | // Match all routes starting with 'admin' 289 | 'admin.*' 290 | 291 | // Match all routes ending with '.admin' 292 | '*.admin' 293 | 294 | // Match all routes containing 'admin' 295 | '*admin*' 296 | ``` 297 | 298 | ### Trait Methods 299 | 300 | The `PermissionTrait` trait adds these methods to your User model: 301 | 302 | ```php 303 | // Get all roles 304 | $user->roles; 305 | 306 | // Get all permissions (including through roles) 307 | $user->permissions(); 308 | 309 | // Assign a role 310 | $user->assignRole('admin'); 311 | 312 | // Revoke a role 313 | $user->revokeRole('admin'); 314 | 315 | // Check if user has a role 316 | $user->hasRole('admin'); 317 | 318 | // Check if user has a permission 319 | $user->hasPermission('users.edit'); 320 | ``` 321 | 322 | ## Exceptions 323 | 324 | The package includes custom exceptions for better error handling: 325 | 326 | - `PermissionAlreadyExists` 327 | - `PermissionDoesNotExist` 328 | - `RoleAlreadyExists` 329 | - `RoleDoesNotExist` 330 | - `UnauthorizedException` 331 | 332 | ## Best Practices 333 | 334 | 1. Use meaningful role slugs and permission routes 335 | 2. Utilize wildcard permissions for related routes 336 | 3. Regularly sync routes with permissions to keep them up to date 337 | 4. Use the cache feature in production for better performance 338 | 5. Handle exceptions appropriately in your application 339 | 340 | ## Contributing 341 | 342 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 343 | 344 | ## License 345 | 346 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 347 | 348 | ## Support 349 | 350 | If you have any questions or issues, please email hossein.hezami@gmail.com instead of using the issue tracker. --------------------------------------------------------------------------------