hdaklue/porter
Lightweight, fast access control for Laravel with roles modeled as domain logic. Assign roles between any Assignable (users/teams) and Roleable (projects/docs) via a Roster. Supports cross-database role assignments for complex, distributed architectures.
Start by installing the package via Composer: composer require hdaklue/porter. Run the migration: php artisan porter:migrate (creates a single porters table for assignments). Define your first role class by extending Hdaklue\Porter\Role\BaseRole (e.g., app/Roles/ProjectManager.php), implementing getName() and optionally business methods like canEditMilestones(). Next, mark your assignable models (e.g., User) and roleable models (e.g., Project) with the respective traits: Assignable and Roleable. Your first use case: assign a user to a project with a role and check access inline:
Porter::assign($user, $project, ProjectManager::class);
if ($user->getAssignmentOn($project)->canEditMilestones()) { /* ... */ }
Look first at the docs/suggested-usage.md and the LeadDeveloper example in the README to internalize the Assignable–Roleable–Roster mental model.
canExportData(int $limit): bool, getRetentionDays(): int), enabling type-safe, discoverable access checks.ProjectManager::class, 'owner') and let Porter handle casting via RoleCast. Prefer class-based keys for IDE autocomplete and refactor safety.User lives on db1 and Project on db2, define separate database connections in config/database.php, and Porter auto-detects and issues multi-connection queries—no extra code required. For custom behavior, override resolveConnection() in your role class.@can('view', $project, ['as' => ProjectManager::class]) and middleware Route::post('/projects/{id}', ...)->middleware('porter:assignable,user,roleable,project,ProjectManager'). Gates automatically resolve assignments if the Gate::after() callback is configured (see docs/laravel-integration.md).Porter::assign($team, [$projectA, $projectB], Developer::class);
Porter::revoke($user, $document, 'viewer');
Porter::revokeAll($user); // revoke all assignments for user
Porter::assign($user, $project, Developer::class) creates one row per pair (user_id, project_id, key)—not a global mapping. Reassigning a different project requires a new assignment.key column in porters is encrypted by default. Use Porter::assign($user, $project, 'plain_key') only if absolutely necessary (and only in dev/test), or set encrypt_keys to false in config/porter.php.Porter::clearCache($user, $project). Disable caching only if absolutely necessary (cache_assignments => false).$user->getAssignmentOn($project) are O(1) cache lookups. If you query manually (e.g., whereHas('assignments')), ensure you’re not triggering N+1—use with('assignments') or Porter::hasAssignment($user, $project, Role::class).resolveAssignable(), resolveRoleable(), and resolveConnection() in your custom BaseRole subclasses to support tenant scoping, soft-deletion awareness, or fallback strategies in distributed systems.Tenant::class) as assignable or roleable where needed. The package validates tenant integrity, but you must ensure tenant_id consistency across models in config/porter.php.$projects = Project::with(['assignments' => fn($q) => $q->whereAssignable($user)])->get();
How can I help you explore Laravel packages today?