Product Decisions This Supports
Feature bets this accelerates
- Per-user ephemeral state without schema churn. Playback positions, "resume where you left off," in-progress wizard steps, last-viewed tab, and per-user UI variants ship without a migration, a new column, a backfill, or a write-heavy table. A "we need a table, an index, and a cleanup job" conversation becomes a one-day story.
- Cheap derived values on detail pages. A stats block, a computed score, or an expensive aggregate can be memoized per model instead of materialized in a view or refreshed by a scheduled job.
- Safe-by-default personalization. Every logged-in user gets their own key namespace automatically, so per-user values can't bleed across users. That's the right default for a PM to inherit, and it's the one thing the package is genuinely opinionated about.
- App-wide knobs when you want them. A per-class API covers "one value for everyone" cases — a featured seed, a rotating daily pick — deliberately rather than accidentally.
- A fast, reversible experiment. One trait and a config file. It's a cheap way to validate whether a personalization feature matters before committing to durable storage.
Build vs buy
- Adopt if the need is small scalar values attached to a model, namespaced per user. The package is a convention layer over Laravel's cache contracts; the convention (key composition, user suffix, TTL policy, kill switch) is the part you'd otherwise re-litigate in every code review. Rough build equivalent: 2–5 engineer-days plus ongoing review friction. MIT makes a fork cheap.
- Build, or use a real column, if you need invalidation on save/delete, whole-model or collection caching, cross-service sharing, or any observability beyond "it's in Redis." The usage docs are explicit that this is not a query cache and not for storing models or collections.
- Resolve the documentation conflict before betting on it. The package blurb advertises automatic invalidation on create/update/delete and "Eloquent query caching," while the usage, facade, and customization docs describe neither and explicitly scope the package to small per-model scalars. Do not put a roadmap item behind invalidation or query caching until someone reads the source and settles it. That single ambiguity is worth more diligence than the rest of the evaluation combined.
Roadmap and use-case fit
- Strong fit: media and learning apps (playback, progress, streaks), consumer SaaS with per-user dashboards, anything with "state that's nice to keep but not worth a row."
- Weak fit: multi-tenant B2B where the namespace should be
tenant_id rather than user_id; billing or entitlement data; anything an auditor or support agent must be able to reconstruct.
- Decisions it should not drive: pricing, permissions, or anything where "the cache expired" is a user-visible correctness bug rather than a cosmetic one.
When to Consider This Package
Adopt only when all of these hold
- Every environment that matters has a persistent cache store (Redis or database). The default
CACHE_STORE may be file or array, which silently loses values on deploy and makes the feature look broken in staging.
- Values are small scalars (int, float, short string, bool) and losing one is a minor UX regression, not an incident.
- Per-user isolation is the correct model. A value written in user A's request is invisible to user B, to guests, and to queued jobs. If a job must read what a request wrote, override the namespace to shared — and treat that as a design decision, not a config tweak. Overriding it the wrong way is the one place a misconfiguration is a security bug rather than a performance bug.
- You accept owning a young dependency: 2 GitHub stars, 0 dependents, single-maintainer provenance, no downstream validation. Practically: vendor it, read the handful of source files, keep a fork ready, and verify the actual latest tag directly — the listed release date (2026-10-14) is in the future, so treat version metadata as unverified.
- Someone owns the cache policy (TTL, when to cache, which values are cacheable) as a shared contract rather than letting each squad invent keys.
Look elsewhere when
- You need shared or global values by default — you'll fight the user-suffix default at every call site.
- You need invalidation tied to model updates — you'd wire observers yourself, and a real column is cleaner at that point.
- You need observability: hit rates, key inventories, per-key ownership, cost attribution. None of that exists here.
- You're multi-tenant by organization, not by user. The suffix is overridable, but you're now customizing the one thing the package is opinionated about.
- You want a battle-tested dependency. This is a prototype-friendly, production-cautious profile — fine for a v1 behind a flag, not a platform primitive.
- Key versioning matters and you can't commit to it. Keys are model + name; if you change the shape or semantics of a value, stale entries can live up to a week (the default TTL). Bake a version into the key name from day one, or shorten the TTL.
Guardrails to set at adoption
- Ship behind a single env kill switch, and make the cache store explicit rather than inherited — otherwise this package's keys compete with session, fragment, and rate-limit keys.
- Write a key registry in the repo: model, key, owner (user vs. shared), TTL, store, system-of-record yes/no. Unowned keys become ghost data. Treat this as a deliverable, not a wiki page.
- Confirm the one-week default TTL per key. That's a product decision, not a technical one.
- Design every read path with a working default so the feature degrades gracefully when caching is off.
How to Pitch It (Stakeholders)
To executives: This is a small, MIT-licensed Laravel helper that lets us store lightweight per-user state — resume points, in-progress steps, per-user variants — in our existing cache instead of adding database tables. It's a days-not-weeks accelerant for a class of small personalization features, and it's reversible: one trait and a config file, so if we outgrow it we delete a use statement. The tradeoff is maturity — young package, negligible adoption, single maintainer — so I'd recommend a contained pilot on one non-critical feature rather than a platform standard. The pitch is "one less table for state we don't need to keep forever," not "a caching strategy." If a feature's correctness depends on the cached value being present, it doesn't belong here.
To engineering: Install is one composer require, a trait on the target model, and a published config with an explicit store pointing at Redis. There are per-record and per-class APIs plus a facade for use outside models. Three things to internalize before the first PR: (1) values are namespaced per logged-in user by default — guests, console, and queued jobs get a different namespace, so override the suffix for anything that must be shared; (2) this is not a query cache and must not store models or collections, whatever the README blurb implies; (3) override the caching predicate, version your key names, and budget half a day to read the source — it's small, and we should assume we may be maintaining a fork. Land it behind a feature flag, verify the tagged release and diff history rather than trusting the listed date, and roll out user-scoped, non-authoritative values first.