tbessenreither/multi-level-cache
A Laravel package for multi-level caching that layers fast in-memory, file, and distributed stores behind one API. Read from the fastest level, fall back to slower ones, and optionally promote hot keys upward to cut latency and backend load. Fully configurable drivers and TTLs.
composer require tbessenreither/multi-level-cache
php artisan vendor:publish --tag="multi-level-cache-config"
config/multi-level-cache.php first. That file is the whole mental model of the package: it defines which cache stores are stacked (typically a fast in-process/array or file L1 in front of Redis/database L2), their TTLs, and the key prefixing strategy. Read it before touching any service class.Invalidator service class since its internals changed.Cache-aside on the multi-level manager. Wrap the expensive path in a remember-style call on the package's cache manager so reads hydrate L1 from L2 transparently. Keep TTLs short on L1 and long on L2 — L1 is the layer most likely to drift.
Generated cached services as the default unit of work. Prefer the package's generator over hand-written cache blocks for anything reused. Each generated service becomes the single place where a key, TTL, and invalidation contract live, which is far easier to review than scattered Cache::remember calls.
Use the new mixin instead of editing generated files. v1.4.2's mixin is the intended extension point: compose extra behavior (logging, metrics, tenant key scoping, custom serialization) onto a generated service without modifying the generated class body. Treat generated services as build artifacts — regenerate freely, keep customization in the mixin. If the mixin doesn't cover your case, wrap the service in a decorator rather than forking the stub.
Invalidate through the invalidator service, not by forgetting keys. The improved invalidator in v1.4.2 is designed to evict across levels in one call. Wire it to domain events (Model::saved, deleted, or your own domain events) so cache correctness is a consequence of the event flow rather than a checklist developers must remember. Always invalidate after the write commits — invalidating inside a transaction that later rolls back leaves you serving data that never existed.
Bind the invalidator behind an interface in a service provider. Injecting an interface lets tests substitute a no-op or recording fake, and lets you swap in a queued/async implementation later without touching call sites.
Queue large invalidations. For bulk operations (imports, mass updates) that touch thousands of keys, dispatch invalidation to a job. Doing it synchronously in a loop is the usual cause of a slow admin action.
Testing pattern. Use the array store for both levels in phpunit.xml, assert the underlying query runs once across two calls, then fire the invalidation event and assert it runs again. That single test catches most multi-level regressions.
Integration tip. If you run Octane, Horizon, or long-lived queue workers, ensure invalidation reaches L1 in those processes too — either via a very short L1 TTL or an event/broadcast that clears local state. This is the pattern most teams skip.
make or upgrade. Everything custom belongs in the v1.4.2 mixin or a decorator.config:cache will bite you. After changing cache levels, TTLs, or prefixes, run php artisan config:cache in deployment and config:clear locally. Stale cached config masquerading as a code bug is the classic false alarm here.afterCommit (or after the transaction closes), never inside it. Also invalidate on bulk paths that bypass Eloquent events — raw DB::table() updates silently skip event-driven invalidation.How can I help you explore Laravel packages today?