cache/prefixed-cache
Decorates any PSR-6 or PSR-16 cache so every key automatically carries a fixed prefix. PrefixedCachePool::create() also supports native tags, and prefixes are safely encoded before joining to keys.
Architecture fit. cache/prefixed-cache is a single-purpose decorator: it wraps a PSR-6 CacheItemPoolInterface and rewrites every key to prefix + key. Architecturally it is a clean, well-bounded seam — no global state, no framework coupling, composition over inheritance. The important caveat for a Laravel shop is that Laravel's cache layer is PSR-16-shaped, not PSR-6. Illuminate\Contracts\Cache\Repository (get/put/remember) is not a CacheItemPoolInterface, and CacheManager does not hand out PSR-6 pools. This package therefore does not "drop into" config/cache.php; it only helps if you already have PSR-6 pools in the process (e.g. php-cache/* adapters, symfony/cache PSR-6 adapters, or a third-party SDK that type-hints Psr\Cache\CacheItemPoolInterface). Any plan that assumes out-of-the-box Redis/Memcached support through Laravel's drivers is wrong and should be corrected before it reaches a roadmap.
Integration feasibility. Feasible in narrow, well-defined cases:
Not feasible as a general Laravel namespacing mechanism — Laravel already supports a per-store prefix in config/cache.php, applied by the cache manager. For the mainstream use case (one app, several stores, tenant-aware keys), the native prefix plus a small tenant-aware key builder is strictly simpler and adds no dependency.
Technical risk. Ranked, highest first:
| Risk | Assessment | Mitigation |
|---|---|---|
| Reserved characters in PSR-6 keys | PSR-6 reserves { } ( ) / \ @ : in keys. The README and internal pitches both use tenant_1:user_123 and acme:api:v1 as examples. On strict pools (Symfony adapters, php-cache integration tests) those keys can be rejected at runtime. This is the single most likely production surprise. |
Standardise on _ or . separators; add a unit test asserting key construction against your real pool before rollout. |
| Laravel PSR-6 gap | No first-class bridge; you must write a custom cache store driver or adapter to reach the package from Cache::store(). |
Budget the adapter as part of the estimate; the decorator is not the hard part. |
clear() / invalidation semantics |
Prefix-scoped clear() is not obviously safe — a decorator either cannot clear only its own keys or must delegate to a full-pool clear(), which in a shared Redis nukes other tenants. Verify against the pinned version; upstream php-cache has historically been conservative here. |
Assume full-pool clear() is unsafe; forbid cache:clear on shared stores; use per-key TTLs and version-stamped prefixes instead. |
| Abandonment / provenance | Repo is marked [READ-ONLY], a split of the php-cache/cache monorepo; issues and PRs live elsewhere (issue tracker and Gitter, both low-activity). 2 stars, 0 dependents. |
Treat as vendorable source, not a supported dependency. |
| Metadata inconsistency | Source says "last release 2022" while the package metadata says 2026-08-20. One of these is wrong; neither is a signal of active development. |
Confirm on Packagist before any ADR references a release date. |
| Key budget | Long compound prefixes (env_tenant_feature_version_) eat into the 64-character minimum key budget the spec guarantees. |
Enforce a max prefix length in config; keep tenant IDs short or hashed. |
Key questions.
Cache facade already solves?clear() semantics on a shared backend?Recommendation: adopt the pattern, not the package. Vendor an equivalent decorator into app/Support/Cache under our own tests. If a dependency is mandated, pin it exactly and wrap it so the swap is one binding change.
Stack fit. Assume Laravel 10/11, PHP 8.2+, Redis via predis/phpredis. Fit is good only in the "shared backend, multiple consumers" scenario. Fit is poor for the default case: Cache::remember() already namespaces by store prefix, and every additional layer between the facade and Redis is a debugging cost during incidents. The package's honest scope is PSR-6 consumers, which in a typical Laravel estate means a handful of libraries, not the whole application.
Migration path.
CacheItemPoolInterface and php-cache/ across composer manifests. If the count is zero, stop — the package has no consumer and the exercise is over.{env}_{tenant|module}_{key} with separators restricted to _ or .; document max lengths per backend; add a validator that rejects reserved characters at the boundary.PrefixedCachePool around one non-critical pool in a test environment. Assert: hit/miss parity, TTL preservation on save/saveDeferred, batch getItems/deleteItems correctness, and behaviour of clear().Illuminate\Cache\Repository that delegates to the PSR-6 pool, registered via Cache::extend() in a service provider and declared in config/cache.php. Keep it thin; it exists to translate PSR-16 calls into getItem/save, nothing more.Compatibility. Backward compatible by construction: prefixing is opt-in per pool, and unprefixed keys are untouched — which is also the migration hazard. Old and new keys will not collide, so a rollout creates a cold cache for migrated paths, not a correctness bug. Plan for a warm-up or accept a latency spike on the first deploy. Forward compatibility is bounded by the package's maintenance status; PSR-6 is stable and frozen in practice, so the realistic break vectors are PHP version deprecations and backend adapter changes, not the interface itself.
Sequencing. Grammar and validation → prototype → Laravel bridge (conditional) → single non-critical store → expand → replace with vendored copy. Explicitly do not sequence this ahead of work that is already planned to consolidate cache infrastructure; if a shared-cluster migration is on the roadmap anyway, fold prefixing into that design rather than shipping it as a standalone change.
Maintenance. Low effort, low attention. The code is small and the interface is frozen, so the realistic maintenance burden is a Composer constraint bump every PHP major. The cost is not maintenance but ownership ambiguity: a read-only mirror repo with issues filed elsewhere and a Gitter channel as the support path means there is no one to escalate to. In practice the team owns it from day one, whether or not that is written down. Document it as internal code in the dependency register.
Support. No vendor, no SLA, no security-response process. For an MIT-licensed ~100-line decorator this is acceptable; for anything touching tenant isolation it is not, unless the team can read and patch the source themselves. Confirm the on-call engineer can debug it — the failure signature (mystery keys appearing in Redis, unexpected InvalidArgumentException on key validation) is not obviously traceable to a decorator without having read it.
Scaling. Negligible CPU cost — string concatenation per operation. Real scaling concerns are structural:
clear(): on a shared cluster, a full-pool clear from a prefixed pool is an estate-wide cache stampede. Restrict the relevant Artisan commands and alert on mass-eviction events.v2_) double memory during overlap. Budget for it or accept a stampede.Failure modes. (a) Reserved-character keys throw at runtime — caught in prototype testing if you test against the real adapter, missed if you test against ArrayAdapter. (b) clear() behaves differently than assumed, either throwing or wiping the shared pool. (c) Silent cold cache after deploy — appears as latency, not errors, so instrument hit ratio before and after. (d) Debuggability regression: a logged key is now the prefixed form, so support runbooks and dashboards that filter on key names need updating. (e) Supply-chain: an abandoned package is a low-probability but unbounded-blast-radius dependency if PHP 9 or a PSR-6-adjacent spec change lands; the vendored alternative has the same risk but you can fix it in an afternoon.
Ramp-up. Under a day for an engineer comfortable with PSR-6. The learning is not the decorator — it is the prefix grammar, the invalidation model, and the PSR-6-vs-PSR-16 boundary in Laravel, which is where most teams get it wrong. Produce a one-page internal note covering the key grammar, the prohibited characters, clear() policy for shared stores, and the swap-to-vendored procedure; that note is the deliverable that makes the adoption reversible.
How can I help you explore Laravel packages today?