wddyousuf/eloquent-autocache
Automatic, self-invalidating Eloquent query caching. Add one trait and reads are cached transparently while any write flushes the model's cache. Uses cache tags or per-model versioning, so it works on every store.
A dead cache store (Redis down, connection refused) previously threw through every cached read, turning a cache outage into an application outage. All store I/O — reads, writes, version counters, stampede locks, SWR, write-time flushes — is now guarded: reads fall through to the database, writes still succeed, and the degradation is logged (throttled to once per process per minute). Database failures inside the query callback still propagate untouched; a flush skipped during an outage is bounded by the TTL.
Found via a live Redis-down probe on a staging deployment. 7 new tests (CacheUnavailableTest) cover list/find/exists/aggregate fallthrough, write survival, DB-error propagation, the stampede-lock path, and log throttling.
Locked reads are never cached. lockForUpdate() / sharedLock() / raw lock() queries were eligible for query-level caching — and on grammars that compile the lock clause away (SQLite), a locked canonical find() could even be served from the row cache. A warm entry then satisfied the read without touching the database, so no row lock was acquired and check-then-write sections relying on it ran unserialized.
Locked queries now bypass the cache in both directions — never served from it, never stored into it:
CachedQueryBuilder::cacheable() rejects any builder whose $lock property is non-null (covers runSelect() and exists()).CachedBuilder::isCanonicalFind() additionally requires no lock, so a locked find() always reaches the database even where the grammar hides the clause from the compiled SQL.The guard checks the builder property rather than the compiled SQL, so it holds across grammars. This also applies to queries explicitly opted in via ->cache() / ->cacheFor(): a pessimistic lock always wins over caching.
Full details in #11. 6 new regression tests (each verified to fail against the unfixed code); suite green across PHP 8.1–8.4 × Laravel 10–13.
Supersedes the withdrawn 0.2.3 (a premature tag caused a Packagist immutability conflict). Same code, cut cleanly from main. Additive; no breaking changes.
sync/attach/detach write is a bare pivot statement that never reaches a cacheable model's builder, so cached relation reads went stale until TTL — unsafe when the pivot gates authorization. Configure autocache.pivot_invalidation.map (pivot table => [cacheable models]); the listener flushes those models on any write to a listed pivot. Opt-in: registered only when the map is non-empty.cache_in_transactions, default true). A read that observes an uncommitted write can cache it; after-commit flushes don't fire on ROLLBACK, so it survives stale until TTL. Default preserves behavior (keeps caching working under RefreshDatabase); set false — globally or per-model via $cacheInTransactions — so open-transaction reads bypass the cache. Writes still flush.Tests: 88 / 150 assertions; CI green across PHP 8.1–8.4 × Laravel 10–13.
Full Changelog: https://github.com/wddyousuf/eloquent-autocache/compare/v0.2.2...v0.2.4
Laravel 13 serializable_classes compatibility (#4). Laravel 13's app skeleton ships config/cache.php with 'serializable_classes' => false, which forbids unserializing any PHP class out of a serializing cache store (file/database/redis/memcached). AutoCache cached PHP objects, so on read-back they returned as __PHP_Incomplete_Class:
->paginate() threw in getCountForPagination().find() silently returned an unusable __PHP_Incomplete_Class instead of the model.get()/count() limped through but smuggled a junk __PHP_Incomplete_Class_Name attribute into every hydrated model.AutoCache now writes only plain data to the cache: query rows are stored as arrays and re-objectified on read, and canonical find() caches a row's raw attributes and rebuilds the model with newFromBuilder(). No configuration change is required — you can leave serializable_classes at its secure default. As a bonus, find() now returns an independent model instance on every call even on the array store.
Full Changelog: https://github.com/wddyousuf/eloquent-autocache/compare/v0.2.1...v0.2.2
CachedBuilder ([@template](https://github.com/template) + [@extends](https://github.com/extends) Builder<TModelClass>) and annotated the trait's builder-returning methods as CachedBuilder<static> (#2).PHPStan/Larastan no longer widen queries on a Cacheable model back to a bare Model — Post::query()->first() resolves to Post again. Downstream apps can drop the stub/baseline workarounds.
Docblock-only; no runtime change. Verified: phpunit 72/72, phpstan clean, pint clean.
illuminate/database, illuminate/support, and illuminate/cache now allow ^13.0 (#3).orchestra/testbench ^11.0, phpunit/phpunit ^12.0.Full test suite (72 tests) passes on Laravel 13 / PHP 8.5.
Initial release of AutoCache — automatic, self-invalidating query caching for Eloquent models.
insert/upsert/insertUsing/updateFrom), and event-suppressing "quiet" writes.file, database, array, …).find() caching that survives writes to other rows.$cacheMode).AutoCache::fake() test double with flush/hit/miss assertions.autocache:flush / clear / warm --all / stats --reset Artisan commands.Requires PHP 8.1+ and Laravel 10, 11, or 12. See the README for docs and a comparison with other caching packages.
How can I help you explore Laravel packages today?