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

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource