HasRole Trait
The HasRole trait turns any Eloquent model into a personable actor that can hold memberships, evaluate permissions, and integrate with Gate, middleware, and Blade.
When to Use
Use HasRole when you need:
- Permission checks on users (or any personable model)
- Assigning / syncing / removing roles in system or memberable scope
- Super-role and owner shortcuts
- Collection-scoped memberships
- Request-scoped memoization and shared permission cache
Example scenarios:
- Staff accounts with system roles
- Tenant members with scoped roles
- API clients that must pass Gate / middleware checks
Namespace
JobMetric\Rolix\Traits\HasRole
Basic Usage
use Illuminate\Foundation\Auth\User as Authenticatable;
use JobMetric\Rolix\Traits\HasRole;
class User extends Authenticatable
{
use HasRole;
}
Permission Evaluation Order
When hasPermission() / getPermissions() run:
- Active super membership →
allow: ['*'] - Owner membership for the given memberable (+ optional collection) →
allow: ['*']in that context only - Load non-expired memberships for the scope
- Resolve roles (and valid ancestors whose rules pass)
- Merge membership + role
allow/deny - Deny wins over allow; supports exact keys,
*, andprefix.*
Methods
hasPermission()
Check whether the person has a permission.
public function hasPermission(
string $permission,
?Model $context = null,
?string $collection = null
): bool
| Parameter | Type | Description |
|---|---|---|
$permission | string | Permission key (e.g. hero.view) |
$context | Model|null | Memberable model, or null for system scope |
$collection | string|null | Optional membership collection filter |
Returns: bool
$user->hasPermission('hero.view');
$user->hasPermission('hero.view', $tenant);
$user->hasPermission('hero.view', $tenant, 'ops');
Internally uses getPermissions() then deny/allow matching (including wildcards).
getPermissions()
Return the effective allow/deny lists for a scope.
public function getPermissions(
?Model $context = null,
?string $collection = null
): array
Returns: array{allow: array<int, string>, deny: array<int, string>}
$perms = $user->getPermissions($tenant);
// ['allow' => ['workspace.*'], 'deny' => ['billing.refund']]
Results are memoized on the model for the current request and, when enabled, stored in Laravel Cache via PermissionCache.
hasRole()
Whether the person has the given role in scope (by model, id, or name).
public function hasRole(
Role|int|string $role,
?Model $context = null,
?string $collection = null
): bool
$user->hasRole($role);
$user->hasRole(12, $tenant);
$user->hasRole('Editor', $tenant, 'ops');
assignRole()
Create a membership through the Membership service.
public function assignRole(
Role|int $role,
?Model $context = null,
array $attributes = []
): Membership
| Parameter | Description |
|---|---|
$role | Role model or id |
$context | Memberable model, or null for system |
$attributes | Extra membership fields (collection, is_owner, allow, deny, expired_at, …) |
Returns: Membership model
$user->assignRole($role);
$user->assignRole($role, $tenant, [
'is_owner' => true,
'collection' => 'ops',
'allow' => ['extra.perm'],
]);
Invalidates permission cache for this personable after store.
removeRole()
Soft-delete memberships matching the role in scope.
public function removeRole(
Role|int $role,
?Model $context = null,
?string $collection = null
): int
Returns: number of destroyed memberships
$count = $user->removeRole($role, $tenant);
syncRoles()
Replace all memberships in scope with the given role ids.
public function syncRoles(
array $roles,
?Model $context = null,
?string $collection = null
): void
$user->syncRoles([$roleA, $roleB->id], $tenant, 'ops');
Removes memberships not in $roles, assigns missing ones, then forgets cache.
forgetRolixCache()
Clear request-scoped memoization and the loaded memberships relation.
public function forgetRolixCache(): void
For shared cache invalidation use PermissionCache::forget($user) (also clears memo).
memberships()
public function memberships(): MorphMany
Morph-many relation as personable.
Scenarios
System admin
$user->assignRole($superRole); // is_super on role
$user->hasPermission('anything'); // true
Tenant owner
$user->assignRole($role, $tenant, ['is_owner' => true]);
$user->hasPermission('anything', $tenant); // true
$user->hasPermission('anything'); // false (different scope)
Wildcards
// role allow: ['hero.*'], deny: ['hero.secret']
$user->hasPermission('hero.view'); // true
$user->hasPermission('hero.secret'); // false
Best Practices
- Prefer
hasPermission()over raw membership queries - Pass memberable context for multi-tenant checks
- Keep permission keys dotted (
module.action) so trees and wildcards work - After external membership changes on an already-loaded model, call
PermissionCache::forget($user)or reload the model