Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Autocache Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config, then opt in one read-heavy model at a time:

composer require wddyousuf/eloquent-autocache
php artisan vendor:publish --tag=autocache-config

Add the package trait to a model (check the exact namespace in vendor/wddyousuf/eloquent-autocache/src — the README's "one trait" is the whole API surface):

use Wddyousuf\AutoCache\Traits\AutoCache; // verify FQCN against the source

class Product extends Model
{
    use AutoCache;
}

Where to look first

  1. config/autocache.php — store selection, default TTL, auto vs opt-in mode, SWR window. This file explains more about runtime behaviour than the README does.
  2. php artisan list autocache — the facade and Artisan commands are your operational handle (flush, warm, status).
  3. The package's own test suite — it is the authoritative spec for what triggers a flush, which is the only thing that can hurt you.

First use case: a model behind a public, read-mostly endpoint (product listing, CMS page, category tree, public profile). Cache it, hit the page twice, confirm the second request skips the query log, then ->update() the row and confirm the next read is fresh. Do that loop before you touch a second model.

The pitch here is narrow and real: you get query-result caching without hand-writing Cache::remember keys and without hand-writing invalidation. Most cache bugs in Laravel apps are missed invalidations, so the value is concentrated in the flush guarantees — not in the caching itself.

Implementation Patterns

Start in opt-in mode, not auto. Auto mode caching every query on a trait-ed model is the fastest path to a surprise. Use the opt-in mode for anything with an admin panel, a queue consumer, or a reporting endpoint, and only widen to auto on genuinely public read paths.

Per-query controls for mixed surfaces. The same model usually serves a cheap detail view and an expensive aggregate. Scope explicitly rather than relying on model-level defaults:

// expensive, cache it hard
Product::cached(ttl: 3600)->where('featured', true)->paginate(24);

// admin/inventory path, always fresh
Product::fresh()/* or ->uncached() */->where('stock', '<', 5)->get();

Row-level caching for detail pages. Route-model-bound show pages are the highest hit-rate/lowest-risk target: one key per row, invalidation scoped to that row's writes. This is where you get the best cache-hit-per-invalidation ratio.

Relationship-aware invalidation — use it, but test it. If ProductResource embeds category.name and review_count, a change to Category or Review must flush the product. The package supports declaring this; every declaration you add is a correctness claim you now own. Add an explicit test per declared relationship:

$product = Product::first();
$product->reviews()->create([...]);
$this->assertSame(1, Product::find($product->id)->reviews()->count()); // reads through cache

Stale-while-revalidate for the expensive stuff. Aggregates and dashboards tolerate a few seconds of staleness in exchange for never paying the cold-cache latency on a request. Set the SWR window on those queries only; leave checkout-adjacent and permission-related reads out of it entirely.

Deploy and ops wiring. Put an autocache flush in your deploy script after migrations, and use the facade in a queued job when you need a targeted purge outside a model write (e.g. a config change that alters presentation). Subscribe to the package events to log cache misses/flushes — at 41 stars and zero dependents, you want visibility into what this thing is deciding on your behalf.

Octane. Long-lived workers make cache-store identity a design decision. Anything relying on the array store per-process will behave inconsistently under Octane; use a shared store in production and confirm the version counters (the fallback path) live in that shared store, not in worker memory.

Gotchas and Tips

Two invalidation mechanisms, two different failure modes. With tags (Redis/Memcached) flushes are immediate and targeted. Everywhere else (file, database, array) the package bumps a per-model version counter — which means the invalidation is correct but the old entries are still on disk until TTL expires. On a file store with a long TTL this quietly accumulates dead cache files. Keep TTLs modest and run cache:prune-stale-tags/cache:clear on a schedule, or just use Redis.

Eloquent events are not a write barrier. The package flushes on model create/update/delete, including quiet and bulk paths that go through Eloquent — but DB::table('products')->update(...), raw SQL, and third-party jobs writing outside Eloquent bypass it entirely. Audit your codebase for query-builder writes to any trait-ed model; if they exist, either convert them or call the facade flush yourself. This is the single most likely source of stale data in a real app.

Transactions are the subtle one. A model event firing inside an open transaction flushes the cache before commit. A concurrent reader can repopulate the key from the pre-commit snapshot, and that stale value survives the commit. Prefer DB::afterCommit() for the flush, or confirm the package defers flushes to after-commit — check the source, don't assume.

Cache keys derive from SQL and bindings. where('id', 1) and where('id', '1') typically produce different keys. Cast consistently, and be aware that inRandomOrder(), now(), and whereDate('created_at', today()) in a query either poison the cache with a time-bound result or guarantee a miss. Filter those out of cached paths.

Scope creep on lazy loads. The trait caches the query you wrote, not the graph reachable from it. $product->category after a cached Product::find() still fires a query. Eager-load what the cached query needs (->with('category')) or the cache saves less than you think.

Tests. Use the array store in phpunit.xml so cache state doesn't leak between test processes, and if the package ships a disable/withoutAutoCache helper, wrap feature tests that assert freshness. Parallel test runs against a shared Redis cache will produce flaky, hard-to-diagnose failures otherwise.

Debugging a "stale" report. Work in this order: (1) is it actually stale, or is it a key mismatch producing a fresh-but-different key? (2) did the write go through Eloquent? (3) was the transaction committed before the read? (4) is the relationship invalidation declared? Log the package events first — that answers (1)–(3) in one pass.

Config quirks. php artisan config:cache after changing autocache.php, and note the Laravel 13 config difference called out in the requirements — per-model overrides via a model property are often more reliable than global config for TTL, since they travel with the code.

Extension points to prefer over forks. Per-model overrides and the facade give you most of what you'd want; the events give you the rest. At this maturity level (41 stars, no dependents, MIT), pin the version in composer.json, read the flush-trigger code once yourself, and keep your own integration tests around invalidation. The package is small enough to audit and cheap enough to replace with a Cache::remember wrapper if a flush path ever fails you — but for a handful of read-heavy public models, it removes a genuinely error-prone chunk of boilerplate.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc