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 Model Caching Laravel Package

mikebronner/laravel-model-caching

Automatically caches Eloquent model and relationship queries, then invalidates them when data changes—no manual cache keys. Add a trait to your models to cut database load on read-heavy pages with self-invalidating, Laravel-native caching.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What it is. A drop-in Eloquent query-result cache: add the Cachable trait (or extend CachedModel) to a model, and get/first/find/all/paginate/pluck/value/exists, aggregations, and eager-loaded with() relationships are cached and auto-invalidated from Eloquent model events (created/updated/deleted). MIT, ~2.4k stars, Laravel 11–13, PHP 8.2+, 335+ integration tests, current release cadence. The value proposition is real but narrow: it removes hand-written cache keys and hand-written invalidation, which is the single most common source of stale-data incidents in Laravel codebases.

Architecture fit. This is an interception layer, not a library you call. It hooks the Eloquent model/query-builder lifecycle, so it sits on your most load-bearing abstraction. That is what makes it cheap to adopt and expensive to reason about: any behavior it doesn't intercept bypasses both the cache and the invalidation. The package is explicit about the boundaries — lazy-loaded relationships, queries with custom select() clauses, and inRandomOrder() are not cached; queries inside transactions are not flushed on commit (manual flushCache() required). Invalidation is tag-granular per model, not row- or query-granular: any write to a model flushes that model's entire cached set. On write-heavy entities (sessions-like tables, counters, activity feeds, users) the cache degenerates into constant cold reads plus invalidation churn, so the "100–900% fewer queries" figure only holds for read-skewed, comparatively static data (CMS pages, catalogs, taxonomy, config-like tables).

Integration feasibility. Low friction to prove, moderate friction to productionize. Install is one composer require plus a trait on a base model; there is no schema change, no migration, no sidecar. The real integration work is environmental and procedural:

  • Cache backend is a hard dependency. Array, file, and database drivers are unsupported. Redis is recommended; Memcached/APC/DynamoDB are supported. DynamoDB works but via logical namespace rotation — multi-tag invalidation is not atomic, stale rows persist until TTL, and high-churn models inflate the table. That rules DynamoDB out for hot models and makes Redis the default answer for most shops.
  • Failure mode defaults to fail-closed. With MODEL_CACHE_FALLBACK_TO_DB=false (the default), a Redis outage becomes an application outage on every cached read path. MODEL_CACHE_FALLBACK_TO_DB=true is essentially mandatory for anything customer-facing; treat it as a required config decision, not a default.
  • Write-path discipline is now a correctness requirement. Any write that doesn't fire Eloquent events — raw DB::table() updates, bulk UPDATE statements, queue jobs using raw SQL, a second application or reporting tool writing the same database, DBA hotfixes — leaves stale rows in cache with no expiry guarantee documented in the README. This is the single largest risk and it is organizational, not technical.
  • Deploy-time cold cache. The maintainers' own contributor guide treats cache key and tag formats as a public contract and flags format changes as a release decision. Upgrades can therefore invalidate every key: the first minutes after a deploy are a full cold-start against the database, with a thundering-herd shape. This must be planned for, not discovered.
  • Cached payloads are serialized models. A deploy that renames columns, changes casts, or reshapes a model can hydrate payloads written by the previous code version. The README does not describe cache versioning on deploy; assume you need a deploy-time flush or prefix rotation.

Technical risk. Ranked:

  1. Silent staleness from non-Eloquent writes and from cache cool-down (withCacheCooldownSeconds() deliberately suppresses invalidation for a window — correct for "recent comments" widgets, unacceptable for balances, inventory, permissions, or anything financial).
  2. Tenant/isolation leakage. Multi-tenancy relies on cache-prefix or the per-model $cachePrefix. use-database-keying separates connections, not tenants sharing one database. A misconfigured or dynamic prefix is a cross-tenant data exposure bug, not a performance bug.
  3. Interception conflicts. It overrides core model/builder methods; any other package that decorates the Eloquent builder (search integrations, audit/logging layers, tenancy packages) is a potential conflict and must be validated, not assumed.
  4. Unbounded Redis growth. TTL behavior is not documented for Redis; the DynamoDB notes concede forever() entries. Plan maxmemory policy, memory budgets, and key-count alerting from day one.
  5. Bus factor. Single-maintainer project (GeneaLabs / Mike Bronner). MIT and a strong test suite mitigate this — you can fork and vendor it — but only if someone on the team has read the internals.
  6. Ecosystem signal is thin. Zero recorded dependents and a low opportunity score indicate a niche, well-executed tool rather than a de facto standard. Don't cite popularity as a risk mitigator in the decision.

Key questions for engineering before approval.

  • What is the default TTL for Redis entries, and is there a supported deploy-time cache flush/version bump?
  • Which models are in scope, and what is the read:write ratio and write path for each? Are all writes Eloquent-mediated, including async workers and any external writers?
  • Does use-database-keying plus a per-tenant prefix fully isolate tenants in our topology?
  • Which installed packages also decorate the Eloquent builder or model lifecycle, and do they collide?
  • Do we accept fail-open-to-database as the default, and what is the alerting path when the fallback triggers?
  • Is the auth User model excluded from the pilot (README explicitly warns against cool-down on it)?

Integration Approach

Stack fit. Clean fit for a conventional Laravel 11–13 monolith or modular monolith on PHP 8.2+ with Redis already in the stack. Poor fit if Redis is absent, if the app is read-light or uniformly write-heavy, if significant data mutation happens outside Eloquent, or if the data is multi-tenant with a cache-prefix story that hasn't been designed yet.

Migration path (additive, reversible, no schema change).

  1. Pilot on a base model. Introduce App\Models\BaseModel with the Cachable trait and migrate 2–3 deliberately chosen models: high read volume, low write volume, non-financial, not User. Keep the default Model for everything else so the blast radius is bounded and rollback is deleting a trait.
  2. Instrument before you scale. Capture query counts and p95 latency per route (Telescope/Debugbar locally, APM in staging), plus Redis hit/miss ratio via cache events or INFO keyspace. Establish baseline numbers so the 100–900% claim is verified on your queries, not taken from the README.
  3. Earn the expansion. Extend the trait only to models that show a measured win and whose write path is confirmed Eloquent-only. Treat every additional model as a separate decision with a named owner.
  4. Harden the write path. Audit for DB::table() writes, bulk updates, and any second writer to the same database; convert or explicitly exclude those models. Add a lint/review checklist item rather than relying on memory.

Compatibility and configuration.

  • Dedicated cache store: declare a model-cache store in config/cache.php and point MODEL_CACHE_STORE at it, so invalidation churn and memory pressure don't evict sessions, queues, or rate-limit keys out of the shared store.
  • MODEL_CACHE_FALLBACK_TO_DB=true in every environment above local.
  • MODEL_CACHE_ENABLED=false in test and local by default, with a small number of tests that explicitly exercise caching against a real Redis.
  • Cool-down left off entirely until a specific route justifies it, and documented per model when enabled.
  • Multi-tenant: per-tenant $cachePrefix (or a globally derived prefix) designed and reviewed before enabling on any tenant-scoped model.

Sequencing across the org.

  1. Spike (1–2 days): pilot model, measure, confirm no builder conflicts.
  2. Infrastructure (2–3 days): Redis store, sizing, eviction policy, fallback flag, alerting, CI Redis service container.
  3. Guardrails (3–5 days): test strategy, write-path audit, deploy-time flush procedure, tenant prefix design, runbook.
  4. Rollout: model-by-model, gated on measured win and write-path confirmation.
  5. Review gate at ~4 weeks: hit ratio, query-count delta, staleness incidents (target zero), Redis memory trend → continue, hold, or revert.

Operational Impact

Maintenance. Low code-level maintenance: no schema, one dependency, MIT, thick test suite, active releases including a Laravel 13 line. The maintenance burden shifts from "write cache keys" to "police the write path and the upgrade." Budget for: reading release notes for cache key/tag format changes (which the maintainers themselves classify as breaking-ish), validating each Laravel major bump against builder interception, and an owned decision log of which models are cached and why. A README-plus-releases maturity rating means you are relying on the test suite and the diff, not on extensive docs — assign one engineer as the designated owner who has read the internals.

Support. The hard support cases are not "the cache is broken" but "the data is wrong and the cache is why." Without deliberate preparation, on-call will not have the vocabulary. Required runbook entries: how to verify a stale-vs-fresh discrepancy, how to flush per-model vs globally, how to disable caching in an incident (MODEL_CACHE_ENABLED=false deploy, or ModelCache::runDisabled()), how to detect a Redis outage vs an invalidation storm, and who owns cache-prefix correctness for tenants. Add a first-response step to every data-correctness escalation: "was this row written outside Eloquent?"

Scaling. Read scaling is the point and it works well for read-skewed data — Redis is the right substrate. Write scaling is where it degrades: per-model tag invalidation means a hot model's cache is under near-constant churn, so the effective cache lifetime approaches zero and you pay invalidation overhead for nothing. Mitigations exist but are commitments, not switches: cool-down windows (buying throughput with deliberate staleness), sharding hot models across cache prefixes, or simply not caching hot-write models. Redis capacity planning needs to assume entries without a documented short TTL — set maxmemory, choose eviction policy consciously (evicting model-cache keys is survivable; evicting sessions is not, which is why a dedicated store matters), and alert on memory slope, not just absolute usage. Expect the post-deploy cold-start spike and, if key formats change, plan a warm-up or staggered rollout.

Failure modes to design for.

  • Redis unavailable → every cached read path fails unless fallback is enabled; verify the fallback path is actually exercised in staging.
  • Stale reads after raw-SQL or external writes → no self-healing; only deploy-time flush or manual intervention.
  • Cross-tenant exposure from a misconfigured or dynamic prefix — treat as a security control with a test, not a config tweak.
  • Thundering herd on deploy or cache flush — the database sees your full read volume at once.
  • Deliberate staleness from cool-down silently outliving its justification (someone sets 300s and nobody revisits it).
  • Transaction-window invisibility — writes inside a transaction that never trigger the expected flush, or reads inside a transaction seeing cached pre-transaction rows.
  • Serialized-payload incompatibility after a schema/cast change ships without a cache rotation.
  • Partial invalidation on DynamoDB (non-atomic tag rotation) if that store is chosen.

Ramp-up. Mechanically trivial (one trait), conceptually non-trivial. Plan roughly half a day of engineering onboarding covering: what is and isn't cached (the explicit non-cached list is the curriculum), why with() is mandatory and lazy loading silently defeats the cache, why select() bypasses it, why DB::table() writes are now a data-integrity concern, and how to disable caching per query and for seeders. Add three lines to PR review: did you write outside Eloquent, did you add a cached model without a measured win, did you change a tenant prefix. Total realistic adoption cost for a mid-size team: 2–4 weeks elapsed including infrastructure, guardrails, and a measured pilot — with a clear revert path (remove the trait) if the pilot doesn't beat baseline query counts and latency on representative traffic.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core