<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Md. Yousuf Hossain</title>
    <description>The latest articles on DEV Community by Md. Yousuf Hossain (@md_yousufhossain_086953).</description>
    <link>https://dev.to/md_yousufhossain_086953</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4032121%2Fac982c07-cba5-4e15-827a-d53cdccc343b.jpg</url>
      <title>DEV Community: Md. Yousuf Hossain</title>
      <link>https://dev.to/md_yousufhossain_086953</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/md_yousufhossain_086953"/>
    <language>en</language>
    <item>
      <title>Stop Serving Stale Data: Automatic Eloquent Query Caching on Any Cache Store</title>
      <dc:creator>Md. Yousuf Hossain</dc:creator>
      <pubDate>Thu, 16 Jul 2026 12:15:50 +0000</pubDate>
      <link>https://dev.to/md_yousufhossain_086953/stop-serving-stale-data-automatic-eloquent-query-caching-on-any-cache-store-2e84</link>
      <guid>https://dev.to/md_yousufhossain_086953/stop-serving-stale-data-automatic-eloquent-query-caching-on-any-cache-store-2e84</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built AutoCache — a Laravel package that caches your reads, flushes on every write, and doesn't need Redis to do it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Query caching in Laravel is one of those things that sounds simple until you actually ship it.&lt;/p&gt;

&lt;p&gt;You cache a query. It's fast. Everyone's happy. Then a background job runs a &lt;code&gt;Model::where(...)-&amp;gt;update(...)&lt;/code&gt;, your cache doesn't notice, and for the next hour your users stare at data that no longer exists. You spend an afternoon adding manual &lt;code&gt;Cache::forget()&lt;/code&gt; calls in seven different places, and you &lt;em&gt;still&lt;/em&gt; miss the one raw insert buried in a service class.&lt;/p&gt;

&lt;p&gt;I got tired of this pattern, so I built &lt;strong&gt;AutoCache&lt;/strong&gt; — an open-source package that makes Eloquent query caching automatic and, crucially, &lt;em&gt;self-invalidating&lt;/em&gt;. You add one trait to a model, and from that point on reads are cached and every write flushes the cache for you. No Redis requirement, no manual cache keys, no stale data.&lt;/p&gt;

&lt;p&gt;This post walks through the problem it solves, how it works, and how it compares to the other caching packages out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two problems with query caching
&lt;/h2&gt;

&lt;p&gt;Most caching setups run into one or both of these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Redis tax.&lt;/strong&gt; A lot of caching libraries only give you automatic invalidation if you're on a taggable store — Redis, Memcached, and friends. If your app runs on the &lt;code&gt;file&lt;/code&gt; or &lt;code&gt;database&lt;/code&gt; cache driver (which is completely reasonable for smaller apps), you either can't use the package or you lose automatic invalidation entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Invalidation that leaks.&lt;/strong&gt; This is the subtle, dangerous one. Many packages hook Eloquent's &lt;em&gt;model events&lt;/em&gt; to know when to clear the cache. That works for &lt;code&gt;save()&lt;/code&gt; and &lt;code&gt;delete()&lt;/code&gt; on a model instance — but it quietly misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bulk writes: &lt;code&gt;Post::where('published', false)-&amp;gt;update([...])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Raw builder writes: &lt;code&gt;insert()&lt;/code&gt;, &lt;code&gt;upsert()&lt;/code&gt;, &lt;code&gt;insertUsing()&lt;/code&gt;, &lt;code&gt;updateFrom()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;increment()&lt;/code&gt; / &lt;code&gt;decrement()&lt;/code&gt; / &lt;code&gt;truncate()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;"Quiet" writes that intentionally suppress events: &lt;code&gt;saveQuietly()&lt;/code&gt;, &lt;code&gt;updateQuietly()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those bypasses model events, so the cache never hears about the change. The result is stale data that appears randomly and is miserable to debug.&lt;/p&gt;

&lt;p&gt;AutoCache is designed to close both gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: one trait
&lt;/h2&gt;

&lt;p&gt;Here's the entire integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Wddyousuf\AutoCache\Traits\Cacheable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Cacheable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole setup. The service provider and &lt;code&gt;AutoCache&lt;/code&gt; facade are auto-discovered, so there's nothing to register. Now watch what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// hits the DB, caches the result&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// served from cache&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                           &lt;span class="c1"&gt;// cached&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                           &lt;span class="c1"&gt;// cached (per-row)&lt;/span&gt;

&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;      &lt;span class="c1"&gt;// flushes Post's cache&lt;/span&gt;

&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// fresh from the DB again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reads are cached transparently. Writes flush automatically. You didn't write a single line of cache-management code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;The trait backs the model with a &lt;code&gt;CachedQueryBuilder&lt;/code&gt;, and everything funnels through two choke points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every SELECT&lt;/strong&gt; goes through the builder's &lt;code&gt;runSelect()&lt;/code&gt; (and &lt;code&gt;exists()&lt;/code&gt;), so &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;first&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;pluck&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;exists&lt;/code&gt;, and even the pagination count query are all cached from one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every write&lt;/strong&gt; goes through the builder's write methods, which flush the model's cache — catching bulk updates, raw inserts, &lt;code&gt;increment&lt;/code&gt;, &lt;code&gt;truncate&lt;/code&gt;, and quiet writes that bypass model events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the hook lives at the &lt;strong&gt;query-builder layer&lt;/strong&gt; rather than on model events, those event-bypassing writes I listed earlier can't slip through. That's the core design decision that makes the invalidation trustworthy.&lt;/p&gt;

&lt;p&gt;For invalidation itself, AutoCache uses &lt;strong&gt;cache tags&lt;/strong&gt; when the store supports them (immediate, targeted flushing) and falls back to a &lt;strong&gt;per-model version counter&lt;/strong&gt; on every other store (&lt;code&gt;file&lt;/code&gt;, &lt;code&gt;database&lt;/code&gt;, &lt;code&gt;array&lt;/code&gt;, …). Either way, it just works — which is what unlocks the "no Redis required" property.&lt;/p&gt;

&lt;h2&gt;
  
  
  The details that matter in production
&lt;/h2&gt;

&lt;p&gt;A caching layer is only as good as its edge cases. AutoCache ships with the ones you'd otherwise have to build yourself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row-level &lt;code&gt;find()&lt;/code&gt; caching.&lt;/strong&gt; Canonical &lt;code&gt;find($id)&lt;/code&gt; lookups are cached under a stable per-row key, so a single row's cache survives writes to &lt;em&gt;other&lt;/em&gt; rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// cached&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// cached&lt;/span&gt;

&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Changed'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// only row 2's cache drops&lt;/span&gt;

&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// still from cache&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// refetched, fresh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transaction-aware invalidation.&lt;/strong&gt; Flushes respect your transactions — they apply after commit, and a rollback leaves the cache untouched, so you never invalidate for a write that didn't actually happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale-while-revalidate&lt;/strong&gt; (Laravel 11+): serve an expired value instantly while it recomputes in the background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stampede protection, TTL jitter, and a result-size guard&lt;/strong&gt; to keep a cold cache or a huge result set from taking your database down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Octane-safe:&lt;/strong&gt; it resets its process-static state between requests, so a long-lived worker never carries cache state across requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-query and per-model control&lt;/strong&gt; when you need to override the defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withoutCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// skip the cache for this query&lt;/span&gt;
&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;cacheFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;       &lt;span class="c1"&gt;// custom TTL for this query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Cacheable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$cacheStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$cacheTtl&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$cacheMode&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'opt-in'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// cache only queries you mark&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing cache behavior without counting SQL
&lt;/h2&gt;

&lt;p&gt;One of my favorite parts. Instead of asserting on query counts or poking at the cache store, you swap in a recording fake and assert on behavior directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Wddyousuf\AutoCache\Facades\AutoCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_publishing_flushes_the_cache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AutoCache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$fake&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertFlushed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are matching &lt;code&gt;assertNotFlushed&lt;/code&gt;, &lt;code&gt;assertHit&lt;/code&gt;, and &lt;code&gt;assertMissed&lt;/code&gt; assertions too.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares to other packages
&lt;/h2&gt;

&lt;p&gt;There are several good caching packages in the ecosystem — &lt;code&gt;laravel-model-caching&lt;/code&gt;, &lt;code&gt;eloquent-query-cache&lt;/code&gt;, and &lt;code&gt;lada-cache&lt;/code&gt; among them. They're all worth knowing. AutoCache's differentiator is narrow and deliberate: &lt;strong&gt;complete write-path coverage on any cache store.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Any cache store:&lt;/strong&gt; &lt;code&gt;laravel-model-caching&lt;/code&gt; needs Redis/Memcached/APC/DynamoDB; &lt;code&gt;lada-cache&lt;/code&gt; is Redis-only; &lt;code&gt;eloquent-query-cache&lt;/code&gt; caches anywhere but only gets &lt;em&gt;automatic&lt;/em&gt; invalidation on a taggable store. AutoCache works on &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;database&lt;/code&gt;, and &lt;code&gt;array&lt;/code&gt; too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-bypassing writes:&lt;/strong&gt; the event-based packages leave stale entries after bulk builder writes, raw inserts, and quiet saves. AutoCache and &lt;code&gt;lada-cache&lt;/code&gt; hook the builder layer instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction-aware invalidation, row-level survival, stale-while-revalidate, stampede protection, TTL jitter, a test fake, and warm/clear/stats Artisan commands&lt;/strong&gt; — this combination isn't something you'll typically find bundled together elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're already all-in on Redis and happy with your current setup, you may not need to switch. AutoCache's sweet spot is teams that either don't run Redis or want invalidation that categorically cannot miss a write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should reach for it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read-heavy apps — blogs, catalogs, dashboards — where the same queries run constantly.&lt;/li&gt;
&lt;li&gt;Projects without Redis that still want real query caching.&lt;/li&gt;
&lt;li&gt;Anyone who's been burned by stale-data bugs from hand-rolled caching.&lt;/li&gt;
&lt;li&gt;Codebases with lots of bulk updates or raw inserts, where invalidation coverage matters.&lt;/li&gt;
&lt;li&gt;Anyone who wants to assert on caching in their test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A couple of honest caveats: &lt;code&gt;cursor()&lt;/code&gt; is intentionally never cached (it streams), and direct &lt;code&gt;DB::table()&lt;/code&gt; writes bypass Eloquent, so you'd call &lt;code&gt;AutoCache::flush(Model::class)&lt;/code&gt; yourself after those.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Requirements are PHP 8.1+ and Laravel 10, 11, 12, or 13 (the latest release, v0.2.2, added Laravel 13 support). Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require wddyousuf/eloquent-autocache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally publish the config if you want to tune TTLs, modes, and the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;autocache-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it — every store works out of the box, with nothing to migrate and no external service to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I built AutoCache because I wanted query caching that I could add and then stop thinking about — no manual invalidation, no Redis prerequisite, and no quiet stale-data bugs six months later. If that resonates, give it a try.&lt;/p&gt;

&lt;p&gt;The project is open source and still young, so feedback genuinely helps. If it's useful, a star on GitHub is appreciated, and issues and contributions are very welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/wddyousuf/eloquent-autocache" rel="noopener noreferrer"&gt;https://github.com/wddyousuf/eloquent-autocache&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading — I'd love to hear how it works out in your projects.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
