RBAC - v1.0.0
Caching
The package maintains two caches to keep authorization checks fast under load. Both use the application's default cache store and are tag-scoped when the store supports tags (redis, memcached).
The two caches
1. Permission names cache
| Property | Value |
|---|---|
| Purpose | Fast-path the Gate::before hook so it only delegates to hasPermissionTo() for abilities that actually match a registered permission |
| Cache key | rbac_permission_names |
| Content | Array of all Permission name and slug values |
| TTL | artisanpack.rbac.cache.permission_names_ttl (default 3600 seconds) |
| Built by | RbacServiceProvider::getCachedPermissionNames() |
| Invalidated by | PermissionObserver on created, updated, deleted |
Without this cache, every $user->can(...) call would hit the database to check if the ability is an RBAC permission. With the cache, the check is an in_array() lookup against a preloaded array.
2. User permissions cache
| Property | Value |
|---|---|
| Purpose | Avoid repeating the recursive role-hierarchy walk on every authorization check for the same user |
| Cache key | per-user, internal to HasPermissions |
| Content | The user's resolved permission collection (union of direct + inherited via parent roles) |
| TTL | artisanpack.rbac.cache.user_permissions_ttl (default 60 seconds) |
| Built by | HasPermissions::hasPermissionTo() on first call |
| Invalidated by | Observer events on role / permission CRUD; manually via $user->flushPermissionCache() |
A short TTL (60s default) keeps the cache fresh without sacrificing the speedup for back-to-back checks within a single request.
Tag scoping
When the cache store implements Illuminate\Cache\TaggableStore (i.e. redis or memcached), both caches are tagged with artisanpack.rbac.cache.tag (default 'rbac'). This lets the package flush only its own entries:
Cache::tags('rbac')->forget('rbac_permission_names');
Cache::tags('rbac')->flush(); // nuke every RBAC cache entry
For non-tagged stores (file, database, array), the package falls back to plain Cache::remember() / Cache::forget() calls. Functionality is identical; you just can't bulk-flush only the RBAC entries — you'd have to wait for TTL or call Cache::flush() (which clears everything).
Tuning the TTLs
The defaults work well for most apps. Tune up or down based on your traffic and update frequency:
| Scenario | Suggested adjustment |
|---|---|
| Low-traffic admin app, permissions change daily | Increase both TTLs (e.g. user_permissions_ttl → 600, permission_names_ttl → 86400) |
| High-traffic public app, permissions rarely change | Keep defaults — the auto-invalidation handles freshness; the cache absorbs the hot path |
| Permissions change via direct DB mutation (no Eloquent) | Drop both TTLs to ~10 seconds so stale data clears quickly, since observers don't fire |
Invalidation matrix
| Mutation path | Permission-names cache | User-permissions cache |
|---|---|---|
Permission::create / update / delete |
flushed (via observer) | flushed (via observer) |
Role::create / update / delete |
not flushed | flushed (via observer) |
$role->permissions()->attach / detach / sync |
not flushed | not flushed automatically |
$user->roles()->attach / detach (raw pivot) |
not flushed | not flushed |
$user->assignRole / removeRole |
not flushed | flushed |
Raw DB::table('permissions')->insert(...) |
not flushed | not flushed |
When you bypass Eloquent, call invalidation yourself:
use Illuminate\Cache\TaggableStore;
use Illuminate\Support\Facades\Cache;
if ( Cache::getStore() instanceof TaggableStore ) {
Cache::tags( config( 'artisanpack.rbac.cache.tag' ) )->flush();
} else {
// Non-taggable store (file, database, array) — flush the known key directly.
Cache::forget( 'rbac_permission_names' );
}
$user->flushPermissionCache();
Cache::tags(...)->flush() throws BadMethodCallException on non-taggable drivers (file, database, array). The capability check above lets the same code path work everywhere.
Disabling the cache
There's no first-class disable flag. The simplest options:
- Set TTLs to
0— every check effectively bypasses the cache. - Use the
arraycache driver in CI / testing — the cache lives only for the request.
In production, leave the cache on. The Gate::before hook is on every authorization check; a cache miss there is expensive enough that disabling the cache hurts more than it helps.