u/Informal-Coyote9142

Released v4.0 stable of a Redis-backed roles & permissions package for Laravel — drop-in API compatible with Spatie
▲ 12 r/PHP

Released v4.0 stable of a Redis-backed roles & permissions package for Laravel — drop-in API compatible with Spatie


TL;DR: Just released v4.0.0 stable of laravel-permissions-redis, a Redis-backed alternative to spatie/laravel-permission. Same API (hasRole, hasPermissionTo, @permission, middleware), different cache strategy: every user→roles→permissions mapping lives in Redis as a SET, and checks become O(1) SISMEMBER calls. ~10x faster on the median request vs Spatie on identical workloads. Three weeks in production without surfacing a new bug.


Why I built it

Spatie is excellent and remains the default — but in apps with heavy authorization on the hot path I kept hitting the same pattern: every request lazy-loads the authenticated user's roles and permissions (one DB query) and then deserializes the cached permission array and scans it. Fine for most apps. Painful for high-traffic APIs.

I wanted a package where the authorization check itself is a constant-time Redis lookup and where invalidation is surgical, not "drop the entire cache and let the next N requests pay the rebuild cost simultaneously".

How it differs

Aspect spatie/laravel-permission laravel-permissions-redis
Warm check Deserialize cached array → scan for match SISMEMBER (O(1))
Cache scope Global permission registry Per-user/per-role SETs
Invalidation forgetCachedPermissions() — drops everything Rewarm only affected user/role
After invalidation Next request rebuilds full cache Cache is already warm
Per-request Hits cache driver every call In-memory cache, no repeated Redis

Benchmarks

Numbers from a standalone benchmark app that runs both packages side-by-side. Apple Silicon, PHP 8.4, predis, 5 warm-ups + 30 measurement runs, GC reset between runs, median reported.

Scenario (1 iter = 27 hasPermissionTo + 4 hasRole + 4 batch ops + 2 collections) Spatie This package Speedup
1 iteration 14.27 ms 1.44 ms 9.92x
10 iterations 144.38 ms 14.39 ms 10.03x
50 iterations 730.88 ms 72.87 ms 10.03x

DB queries per request: 4 → 1 (75% reduction).

The ~10x holds because both strategies scale linearly — what differs is the per-iteration constant (4 DB queries vs 1 Redis lookup).

What 4.0 brings

  • Permission group metadata preserved in Redis with atomic rebuild (replacePermissionGroups)
  • Guard-aware Blade directives and full multi-guard isolation
  • Queue-backed cache warming (WarmUserCacheJob)
  • permissions-redis:migrate-from-spatie artisan command for one-shot data migration
  • Contract suite running against a real Redis instance (not mocks) — auto-skips when Redis isn't available
  • Multi-tenancy (stancl/tenancy integration + custom resolvers), UUID/ULID support, Laravel Octane support

PHP 8.3+, Laravel 11/12/13, PHPStan level max, Pest 4, mutation testing via Infection.

When NOT to use it

Honest list, because this matters more than the speedups:

  • You don't run Redis. This package requires it. If Redis isn't in your stack, Spatie's database/file-cache approach is the right choice.
  • You need getDirectPermissions() / getPermissionsViaRoles() at runtime. This package merges them by design.
  • You need Spatie's teams feature. The multi-tenancy here is different — Redis namespace isolation, not team-scoped role assignments.
  • You're on PHP < 8.3 or Laravel < 11. No backport planned.
  • Authorization isn't a bottleneck. If your app is low-traffic, the DB-backed approach is fine — don't add Redis just for this.

Links

Happy to answer architecture questions or hear what breaks if you try it. Roast welcome.

u/Informal-Coyote9142 — 3 days ago