pkc / role-permission
A dynamic role and permission management package for Laravel.
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/pkc/role-permission
Requires
- php: ^8.2
- illuminate/auth: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
README
A lightweight, dynamic role and permission management package for Laravel. This package allows you to manage roles and permissions dynamically via the database, with support for hierarchical permissions and granular access control.
✨ Features
- 🔐 Dynamic Roles & Permissions: Store and manage roles and permissions in the database
- 🌳 Hierarchical Permissions: Supports parent-child relationships for permissions (e.g., a "Module" permission containing multiple "Action" permissions)
- ⚡ Performance Optimized: Built-in caching support for faster permission checks
- 🚀 Easy Integration: Simple traits for
UserandRolemodels - 🎯 Route Access Control: Helpers to check access based on route names
- 🔒 Production Ready: Validation, error handling, and security features
📋 Requirements
- PHP >= 8.2
- Laravel >= 11.0
- Composer
📦 Installation
You can install the package via composer:
composer require pkc/role-permission
🔧 Setup
1. Publish Configuration (Optional)
php artisan vendor:publish --tag=role-permission-config
This will create config/role-permission.php where you can customize settings.
2. Run Migrations
The package comes with migrations for roles, permissions, and the pivot role_permissions table. Run them using:
php artisan migrate
3. Prepare your Models
User Model
Add the HasRoles trait to your User model:
namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Pkc\RolePermission\Traits\HasRoles; class User extends Authenticatable { use HasRoles; // Your existing code... }
Make sure your users table has a role_id foreign key column.
Role Model (Optional)
If you want to extend or customize the Role model:
namespace App\Models; use Pkc\RolePermission\Models\Role as BaseRole; class Role extends BaseRole { // Your custom logic here }
💡 Complete Example Flow
Here's a complete step-by-step example of setting up and using the package in a blog application:
Step 1: Setup and Migration
# Install the package composer require pkc/role-permission # Run migrations php artisan migrate
Step 2: Configure User Model
// app/Models/User.php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Pkc\RolePermission\Traits\HasRoles; class User extends Authenticatable { use HasRoles; // Your existing code... }
Step 3: Create Roles
use Pkc\RolePermission\Models\Role; // Create roles $admin = Role::create([ 'name' => 'Administrator', 'slug' => 'admin', 'description' => 'Full system access', 'status' => true, ]); $editor = Role::create([ 'name' => 'Editor', 'slug' => 'editor', 'description' => 'Can create and edit posts', 'status' => true, ]); $author = Role::create([ 'name' => 'Author', 'slug' => 'author', 'description' => 'Can create posts', 'status' => true, ]);
Step 4: Create Permissions (Hierarchical Structure)
use Pkc\RolePermission\Models\Permission; // Create Posts Module (parent permission) $postsModule = Permission::create([ 'name' => 'Posts', 'slug' => 'posts', 'type' => 'module', 'route_name' => 'posts.index', 'order' => 1, 'status' => true, ]); // Create action permissions (children) $viewPosts = Permission::create([ 'name' => 'View Posts', 'slug' => 'posts.view', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.show', 'order' => 1, 'status' => true, ]); $createPosts = Permission::create([ 'name' => 'Create Posts', 'slug' => 'posts.create', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.create', 'order' => 2, 'status' => true, ]); $editPosts = Permission::create([ 'name' => 'Edit Posts', 'slug' => 'posts.edit', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.edit', 'order' => 3, 'status' => true, ]); $deletePosts = Permission::create([ 'name' => 'Delete Posts', 'slug' => 'posts.delete', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.destroy', 'order' => 4, 'status' => true, ]);
Step 5: Assign Permissions to Roles
// Admin gets all permissions $admin->syncPermissions([ $postsModule->id, $viewPosts->id, $createPosts->id, $editPosts->id, $deletePosts->id, ]); // Editor can view, create, and edit (but not delete) $editor->syncPermissions([ $viewPosts->id, $createPosts->id, $editPosts->id, ]); // Author can only view and create $author->syncPermissions([ $viewPosts->id, $createPosts->id, ]);
Step 6: Assign Roles to Users
use App\Models\User; // Assign admin role to a user $user1 = User::find(1); $user1->update(['role_id' => $admin->id]); // Assign editor role $user2 = User::find(2); $user2->update(['role_id' => $editor->id]); // Assign author role $user3 = User::find(3); $user3->update(['role_id' => $author->id]);
Step 7: Use in Controller
// app/Http/Controllers/PostController.php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class PostController extends Controller { public function index() { // Check permission if (!auth()->user()->hasPermission('posts.view')) { abort(403, 'You do not have permission to view posts.'); } $posts = Post::all(); return view('posts.index', compact('posts')); } public function create() { if (!auth()->user()->hasPermission('posts.create')) { abort(403); } return view('posts.create'); } public function store(Request $request) { if (!auth()->user()->canAccessRoute('posts.create')) { abort(403); } // Create post logic... } public function edit($id) { if (!auth()->user()->hasPermission('posts.edit')) { abort(403); } $post = Post::findOrFail($id); return view('posts.edit', compact('post')); } public function destroy($id) { if (!auth()->user()->hasPermission('posts.delete')) { abort(403); } // Delete post logic... } }
Step 8: Use in Blade Templates
{{-- resources/views/posts/index.blade.php --}} {{-- Show posts menu if user has posts permission --}} @if(auth()->user()->hasPermission('posts')) <li><a href="{{ route('posts.index') }}">Posts</a></li> @endif {{-- Show create button if user has create permission --}} @if(auth()->user()->hasPermission('posts.create')) <a href="{{ route('posts.create') }}" class="btn btn-primary">Create Post</a> @endif <table> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td> {{-- Show edit button if user has edit permission --}} @if(auth()->user()->hasPermission('posts.edit')) <a href="{{ route('posts.edit', $post) }}">Edit</a> @endif {{-- Show delete button if user has delete permission --}} @if(auth()->user()->hasPermission('posts.delete')) <form action="{{ route('posts.destroy', $post) }}" method="POST"> @csrf @method('DELETE') <button type="submit">Delete</button> </form> @endif </td> </tr> @endforeach </table>
Step 9: Testing the Flow
// Example: Testing permissions $user = User::find(2); // Editor user // These will return true $user->hasPermission('posts.view'); // true $user->hasPermission('posts.create'); // true $user->hasPermission('posts.edit'); // true $user->hasPermission('posts'); // true (parent module) // This will return false $user->hasPermission('posts.delete'); // false // Check role $user->hasRole('editor'); // true $user->hasAnyRole(['admin', 'editor']); // true // Check multiple permissions $user->hasAnyPermission(['posts.edit', 'posts.delete']); // true (has edit) $user->hasAllPermissions(['posts.edit', 'posts.delete']); // false (missing delete)
Step 10: Dynamic Permission Management (Optional)
You can also manage permissions dynamically through your admin panel:
// In your admin controller public function updateRolePermissions(Request $request, Role $role) { $permissionIds = $request->input('permissions', []); // Validate permission IDs exist $validIds = Permission::whereIn('id', $permissionIds)->pluck('id')->toArray(); // Sync permissions $role->syncPermissions($validIds); // Clear cache $role->clearPermissionCache(); return redirect()->back()->with('success', 'Permissions updated successfully'); }
This complete flow demonstrates:
- ✅ Setting up the package
- ✅ Creating roles and permissions
- ✅ Building hierarchical permission structure
- ✅ Assigning permissions to roles
- ✅ Assigning roles to users
- ✅ Using permissions in controllers
- ✅ Using permissions in Blade templates
- ✅ Testing and verification
💡 Usage Examples
Creating Roles
use Pkc\RolePermission\Models\Role; // Create a new role $adminRole = Role::create([ 'name' => 'Administrator', 'slug' => 'admin', 'description' => 'Full system access', 'status' => true, ]); $editorRole = Role::create([ 'name' => 'Editor', 'slug' => 'editor', 'description' => 'Can edit content', 'status' => true, ]);
Creating Permissions
use Pkc\RolePermission\Models\Permission; // Create a parent permission (module) $postsModule = Permission::create([ 'name' => 'Posts Management', 'slug' => 'posts', 'type' => 'module', 'route_name' => 'posts.index', 'order' => 1, 'status' => true, ]); // Create child permissions (actions) $viewPosts = Permission::create([ 'name' => 'View Posts', 'slug' => 'posts.view', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.show', 'order' => 1, 'status' => true, ]); $editPosts = Permission::create([ 'name' => 'Edit Posts', 'slug' => 'posts.edit', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.edit', 'order' => 2, 'status' => true, ]); $deletePosts = Permission::create([ 'name' => 'Delete Posts', 'slug' => 'posts.delete', 'type' => 'action', 'parent_id' => $postsModule->id, 'route_name' => 'posts.destroy', 'order' => 3, 'status' => true, ]);
Assigning Roles to Users
$user = User::find(1); $user->role_id = $adminRole->id; $user->save(); // Or using update $user->update(['role_id' => $editorRole->id]);
Assigning Permissions to Roles
$editorRole = Role::find(2); // Sync permissions (replaces all existing permissions) $editorRole->permissions()->sync([$viewPosts->id, $editPosts->id]); // Or use the helper method $editorRole->syncPermissions([$viewPosts->id, $editPosts->id]);
Checking Permissions in Code
// Check if user has a specific permission if ($user->hasPermission('posts.edit')) { // User can edit posts } // Check if user can access a route if ($user->canAccessRoute('posts.edit')) { // User can access this route } // Check if user has any of the given permissions if ($user->hasAnyPermission(['posts.edit', 'posts.view'])) { // User has at least one permission } // Check if user has all of the given permissions if ($user->hasAllPermissions(['posts.edit', 'posts.delete'])) { // User has all permissions } // Check if user has a specific role if ($user->hasRole('admin')) { // User is an admin } // Check if user has any of the given roles if ($user->hasAnyRole(['admin', 'editor'])) { // User has at least one role }
Using in Blade Templates
{{-- Check permission --}} @if(auth()->user()->hasPermission('posts.edit')) <a href="{{ route('posts.edit', $post) }}">Edit</a> @endif {{-- Check route access --}} @if(auth()->user()->canAccessRoute('posts.index')) <a href="{{ route('posts.index') }}">All Posts</a> @endif {{-- Check multiple permissions --}} @if(auth()->user()->hasAnyPermission(['posts.edit', 'posts.delete'])) <div class="actions"> <a href="{{ route('posts.edit', $post) }}">Edit</a> <a href="{{ route('posts.delete', $post) }}">Delete</a> </div> @endif {{-- Show sidebar menu based on permission --}} @if(auth()->user()->hasPermission('posts')) <li><a href="{{ route('posts.index') }}">Posts</a></li> @endif
Using in Controllers
namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class PostController extends Controller { public function index() { // Check permission if (!auth()->user()->hasPermission('posts.view')) { abort(403, 'You do not have permission to view posts.'); } // Your code... } public function edit($id) { if (!auth()->user()->canAccessRoute('posts.edit')) { abort(403); } // Your code... } }
Using in Middleware
namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class CheckPermission { public function handle(Request $request, Closure $next, $permission) { if (!auth()->user()->hasPermission($permission)) { abort(403, 'Access denied'); } return $next($request); } }
Register in bootstrap/app.php:
$middleware->alias([ 'permission' => \App\Http\Middleware\CheckPermission::class, ]);
Use in routes:
Route::get('/posts', [PostController::class, 'index'])->middleware('permission:posts.view');
Hierarchical Permissions
The package supports hierarchical permissions. If a permission is a "module" type and has children, checking the parent permission will return true if any of its children are assigned to the role.
// If 'posts' is a module and 'posts.edit' is assigned to the role $user->hasPermission('posts'); // returns true (because descendant 'posts.edit' is assigned) $user->hasPermission('posts.edit'); // returns true (directly assigned) // This is useful for sidebar visibility // If user has access to any action within a module, the module appears in the sidebar
Querying Roles and Permissions
use Pkc\RolePermission\Models\Role; use Pkc\RolePermission\Models\Permission; // Get only active roles (excludes master-admin by default) $roles = Role::visible()->active()->get(); // Get root permissions (no parent) $rootPermissions = Permission::root()->get(); // Get only active permissions $activePermissions = Permission::active()->get(); // Get permissions with their children $permissions = Permission::root()->with('children')->get();
Cache Management
Permission checks are cached by default for better performance. To clear cache:
// Clear cache for a specific role $role->clearPermissionCache(); // Or clear all cache php artisan cache:clear
You can configure caching in config/role-permission.php:
'cache_enabled' => true, 'cache_ttl' => 3600, // Cache for 1 hour
🔒 Security Features
- Validation: Permission assignments are validated before saving
- Circular Reference Prevention: Automatic detection and prevention of circular permission hierarchies
- Error Handling: Proper exceptions for better error handling
📝 Configuration
You can customize the package behavior by publishing the config file:
php artisan vendor:publish --tag=role-permission-config
Available configuration options:
user_model: Your User model classmaster_admin_slug: Slug for master admin role (default: 'master-admin')cache_enabled: Enable/disable caching (default: true)cache_ttl: Cache time-to-live in seconds (default: 3600)table_names: Custom table names (optional)
🐛 Error Handling
The package provides specific exceptions:
use Pkc\RolePermission\Exceptions\CircularReferenceException; use Pkc\RolePermission\Exceptions\InvalidPermissionAssignmentException; use Pkc\RolePermission\Exceptions\PermissionNotFoundException; try { $role->syncPermissions([1, 2, 3]); } catch (InvalidPermissionAssignmentException $e) { // Handle invalid permission IDs }
📚 API Reference
User Model Methods (HasRoles trait)
hasPermission(string $slug): bool- Check if user has a permissioncanAccessRoute(string $routeName): bool- Check if user can access a routehasAnyPermission(array $slugs): bool- Check if user has any of the given permissionshasAllPermissions(array $slugs): bool- Check if user has all of the given permissionshasRole(string $slug): bool- Check if user has a specific rolehasAnyRole(array $slugs): bool- Check if user has any of the given rolesrole(): BelongsTo- Get the user's role relationship
Role Model Methods
hasPermission(string $slug): bool- Check if role has a permissionpermissions(): BelongsToMany- Get permissions relationshipsyncPermissions(array $permissionIds, bool $recursive = false): void- Sync permissionsclearPermissionCache(): void- Clear permission cache for this roleisMasterAdmin(): bool- Check if role is master adminscopeVisible($query)- Exclude master admin from queriesscopeActive($query)- Get only active roles
Permission Model Methods
parent(): BelongsTo- Get parent permissionchildren(): HasMany- Get child permissionsgetAllDescendantIds(): array- Get all descendant permission IDsgetAncestors(): Collection- Get all ancestor permissionstoTreeArray(): array- Get permission as tree structurescopeRoot($query)- Get only root permissionsscopeActive($query)- Get only active permissions
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
The MIT License (MIT). Please see License File for more information.
👤 Author
PKC
- Email: laradev.sumon@gmail.com
📊 Hierarchical Permission Structure Example
Here's an example of a real-world hierarchical permission structure:
FMDF
├── Stock Management
│ ├── Lot Entry
│ │ ├── Add
│ │ ├── Edit
│ │ ├── Delete
│ │ └── View
├── Order Management
├── Add
├── Edit
├── Delete
└── View
Flow Example:
FMDF -> Stock Management -> Tree Stock -> Add
FMDF -> Order Management -> Add
This means:
- FMDF is the root module
- Stock Management is a child module of FMDF
- Tree Stock is a child module of Stock Management
- Add is an action permission under Tree Stock
When a user has the "Tree Stock -> Add" permission, they automatically get access to:
- Tree Stock (parent module)
- Stock Management (grandparent module)
- FMDF (root module)
This hierarchical structure allows for:
- ✅ Flexible permission management
- ✅ Easy sidebar menu visibility control
- ✅ Granular access control at each level
- ✅ Automatic parent permission inheritance