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

Laravel Eloquent Query Cache Laravel Package

helixdigital-io/laravel-eloquent-query-cache

A Laravel package that caches Eloquent query results to cut database load and speed up repeated reads. It integrates with the query builder to store and reuse results, with cache invalidation support to help keep data fresh.

View on GitHub
Deep Wiki
Context7

Getting Started

helixdigital-io/laravel-eloquent-query-cache sits between Eloquent and your cache store: it hashes the SQL + bindings of a query built through a model using the trait, stores the result, and invalidates those entries automatically when that model is written to.

  1. Install and publish config

    composer require helixdigital-io/laravel-eloquent-query-cache
    php artisan vendor:publish   # pick the package's service provider / tag from the list
    
  2. Use a taggable cache store. Invalidation is tag-based, so the default file/database drivers won't work out of the box. Set Redis, Memcached, DynamoDB or array as the package's store in config/eloquent-query-cache.php (or whichever store key the published config exposes), independent of your app's default store.

  3. Opt in per model — the trait is not global:

    use HelixDigital\LaravelEloquentQueryCache\Traits\QueryCacheable;
    
    class Article extends Model
    {
        use QueryCacheable;
    
        protected static $cacheFor = 3600; // seconds; required
    }
    
  4. First real use case: a read-heavy public listing page.

    $articles = Article::query()
        ->cacheFor(300)
        ->with('author')
        ->where('published', true)
        ->latest()
        ->paginate(20);
    
  5. Where to look first in the source: the QueryCacheable trait (the whole public API lives there: cacheFor, cacheTags, cachePrefix, dontCache, flushCache), then the custom builder it returns, then the model event hooks that perform invalidation. Those three files explain 95% of the behaviour you'll debug.

Start with one or two high-traffic, low-write models. Don't blanket-apply the trait across the domain on day one.

Implementation Patterns

Per-model default TTL, per-query override. Set a sane $cacheFor on the model, then shorten or lengthen where it matters: Article::cacheFor(60)->where(...) for volatile feeds, Article::cacheFor(86400) for reference data. Chain it before the terminal method — it returns the builder.

Tag by tenant/user when results are scoped. A cache key is only as specific as the SQL. If you scope by tenant_id in a global scope the key already differs; if you scope in PHP or via a container-resolved "current user", it does not — pass ->cacheTags(['tenant:'.$tenant->id]) (or set $cacheTags statically) so you can flush per tenant.

Invalidation is event-driven — keep writes going through models. save(), create(), update() on an instance, delete(), restore() and forceDelete() fire the events that flush the model's tags. Anything that bypasses events (see Gotchas) needs a manual Article::flushCache().

Manual flush after batch operations. Imports, bulk reassignments and console commands should end with a targeted flushCache() on the affected model class, not php artisan cache:clear.

Relationships need the trait too. Eager loading works, but the related model's queries are only cached if that model also uses QueryCacheable. If Author isn't opted in, ->with('author') still hits the DB for the author rows on every cached article query's first build — cached, but the related fetch isn't independently reusable.

Bypass deliberately, not accidentally. ->dontCache() is the per-query escape hatch — use it for admin screens, exports, and anywhere a user just wrote data and expects to see it. In local/staging, either point the package store at array or disable caching in a service provider so stale results don't confuse debugging.

Pagination is keyed per page. ->paginate() caches each page separately, which is usually what you want; just remember the key includes the page number, so a 50-page listing costs 50 entries the first time it's crawled. Bound it with a short TTL on paginated queries.

Aggregates and counts are cached like any other query. count(), exists(), sum(), and withCount() all flow through the cached builder — cheap wins for dashboards and badges.

Testing. Configure the package store to array in phpunit.xml, and Cache::flush() in setUp() if you have tests that assert fresh reads across test methods. For tests asserting write-then-read behaviour inside a single test, ->dontCache() is less surprising than relying on invalidation timing.

Warm the cache after deploy rather than letting the first request per key take the hit — a small artisan command that runs the top few listing queries with cacheFor() set does the job, or just accept the cold-start.

Gotchas and Tips

  • A missing $cacheFor throws. The package needs a TTL; there is no safe default. Every model using the trait must declare it (or you override the resolution).
  • Non-taggable stores. On file or database you'll get failures or unbounded staleness. Either move to Redis/Memcached or enable the plain-key mode the config exposes (usually cache_use_plain_key), which disables tag-based invalidation — meaning you must flush manually. Prefer Redis.
  • DB::table(), raw queries and ->toBase() bypass the cache entirely. So do DB::select() and anything from a repository that drops to the query builder.
  • Model::where(...)->update() and ->delete() do not fire model events. This is the number one source of stale cache in production. Either iterate (->each->update() / ->each->delete()) or call flushCache() immediately after. Same for insert(), upsert(), and increment() on the builder.
  • Transactions flush early. Invalidation fires inside the transaction, so a concurrent reader can repopulate the cache from pre-commit data and then be served it after commit. For write-heavy paths inside transactions, wrap the read or flush again after commit (DB::afterCommit).
  • Non-deterministic SQL poisons the cache. orderByRaw('RAND()'), NOW(), CURRENT_TIMESTAMP comparisons, and session-dependent SQL produce keys that look stable but return stale/incorrect data. Use dontCache() or move the randomness into PHP.
  • Read replicas + caching can serve data older than the replica lag. If you can't tolerate that, don't cache that model.
  • Redis memory growth. Tag sets accumulate per model and per tag; set a TTL, an eviction policy suited to a cache (allkeys-lru), and use the package's cache_prefix so keys are identifiable. redis-cli --scan --pattern '*<prefix>*' | head is your friend when verifying what was actually stored.
  • Debugging checklist: confirm the store supports tags → confirm the model has the trait and $cacheFor → confirm the query is built from the model (not DB) → inspect keys in Redis → check whether the write that "should" have invalidated went through a model event.
  • Multi-tenant / multi-app sharing one Redis: separate database or a distinct cache_prefix per app, otherwise flushes cross boundaries.
  • Extension points: the trait is designed to be overridden — subclass it in your own base model to standardise TTLs, inject tenant tags, or normalise cache keys across your domain. flushCache() and cacheTags() are the two you'll wrap most often.
  • On 3.4.1: the release is a patch with no documented API, config or behaviour changes. Treat it as a drop-in: bump, run the suite, and re-verify your cache store still resolves correctly if you pin Laravel/PHP versions. No assessment-affecting changes; revisit if a minor release lands with new config keys or a tag/invalidation change.
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast