cache/illuminate-adapter
This package exposes an Illuminate cache store through PSR-6 and PSR-16, with tag support and hierarchical keys. Version 2 supports Illuminate 11 through 13. Pass any Illuminate\Contracts\Cache\Store implementation to the pool constructor.
Architecture fit. The package is a thin PSR-6 bridge: IlluminateCachePool wraps any Illuminate\Cache\Store (Array, File, Redis, Database, Memcached) and exposes CacheItemPoolInterface. Architecturally it is the right shape for the only problem it solves — letting a PSR-6 consumer (Doctrine ORM second-level cache, Symfony components, third-party libraries that type-hint Psr\Cache\CacheItemPoolInterface) reuse the cache backend Laravel is already configured and paid for. It does not replace Laravel's cache API, it does not add a driver, and it does not manage cache infrastructure. TPM framing: this is a low-cost, narrow-coupling decision that buys interoperability, not performance or features.
Two structural facts materially shape the assessment:
[READ-ONLY] — it is a subtree split of the php-cache/cache monorepo, not an independently maintained project. All issues, PRs, and release cadence are governed by the parent repo's maintainers. There is no per-package roadmap we can influence, and no per-package issue tracker of our own.Store, not a Repository or CacheManager. That means repository-level behaviour (event dispatch, repository-configured default TTL, Cache::remember* sugar, repository-level tag handling) is bypassed when a consumer goes through the pool. This is easy to miss in a design review and is the single most likely source of subtle behavioural drift.Integration feasibility. Very high. composer require cache/illuminate-adapter, then wrap a store instance:
$pool = new IlluminateCachePool(Cache::store('redis')->getStore());
No config migration, no driver replacement, no data migration — cache data is disposable by definition, so a cold-start cutover is acceptable. Effort for a single consumer is measured in hours, not sprints. The real cost sits in verification, not integration.
Technical risk. Ranked by what would actually hurt:
DateTimeInterface (with null = never expire); Laravel TTLs are relative seconds with different "forever" conventions. Adapters get this wrong at the edges. Similarly, PSR-6 mandates serializable values and Illuminate stores often serialize again — expect redundant encoding and a small CPU/allocation cost, plus surprises if anything stores non-serializable objects today.TaggableStore), not universally available, and PSR-6 has no native tag concept — it arrives via php-cache's TaggablePoolInterface. Do not repeat downstream claims about tag-based invalidation until an integration test proves it for each driver we intend to use.SimpleCache) for application-level caching. Standardizing on PSR-6 is a multi-year bet on an interface that is stable but not growing. It is the correct bet only where a concrete consumer requires it.cache/integration-tests against the adapter in our own CI as the acceptance gate rather than trusting the badge.Key questions.
ArrayStore?getMultiple/deleteMultiple/has/getMetadata implemented correctly, or are they loop-based fallbacks with per-item round trips?Stack fit. Fit is narrow and legible; say yes when a PSR-6 consumer exists, no otherwise.
| Scenario | Fit | Rationale |
|---|---|---|
Doctrine ORM / Symfony component needing CacheItemPoolInterface in a Laravel app |
Strong | This is the canonical use case; avoids running two cache stacks |
| Shared library used by both Laravel and non-Laravel services | Strong | One interface, both runtimes; avoids branching in the library |
| Phased PSR-6 adoption in a legacy Laravel monolith | Moderate | Useful as a stepping stone, but no data migration is needed, so the "phased" story is mostly about consumer-by-consumer rollout |
| Pure Laravel app with no external cache consumers | None | Adds indirection over a richer native API (tags, locks, events, remember) |
| Latency-critical path | Weak | Extra abstraction layer plus likely double serialization; measure before adopting |
Anything needing atomic locks or remember semantics |
None | Not expressible in PSR-6; keep those on native Laravel |
Migration path.
Psr\Cache / CacheItemPoolInterface type hints. If nothing requires it, stop here and record the decision — a documented "no" is a valid outcome and cheap to produce.cache/integration-tests against ArrayStore and against our production driver; explicitly test TTL edge cases (null TTL, sub-second, past expiry), getMultiple/deleteMultiple, and tag deletion if tags are in scope. Capture results as a one-page conformance record.cache/illuminate-adapter; native Laravel cache remains the default for first-party code." This is the artefact that stops the next team from re-litigating the choice.Compatibility.
ArrayStore (tests), RedisStore (production target), DatabaseStore if used. Treat FileStore as single-node only and never for tagged data across instances. Do not assume Memcached/Dynamo parity without evidence.remember, repository events) so consumers don't reach for it.Sequencing within a delivery plan. Verification and pilot run in parallel with normal feature work — roughly 3–5 engineer-days end to end for the first consumer, then hours per additional consumer. Do not put a framework upgrade and this adoption in the same release train; if the adapter's compatibility is unproven on the target Laravel version, upgrade first, then adopt.
Maintenance. Ongoing cost is low but non-zero, and it must be assigned, not assumed. Budget roughly 4–8 hours per quarter: check upstream activity, re-run conformance tests on framework/PHP bumps, and confirm pinned versions still resolve. Because this is a read-only split, contributions go to php-cache/cache, which means our fix turnaround is someone else's release cycle. Decide up front whether our policy is (a) pin and accept latency, or (b) maintain a small vendor fork. For ~100 LOC, (b) is genuinely viable and should be pre-approved rather than negotiated under pressure. Note that with zero dependents upstream, our bug reports may not be prioritized — plan accordingly.
Support. Documentation is minimal (install plus a five-line example), and the README's tagging language is inherited boilerplate that should not be trusted at face value. We should write our own half-page internal runbook: supported drivers and versions, what the pool does and does not expose, the exact line to wrap a store, the TTL mapping we rely on, and the escalation path. Do not plan on community support: 11 stars and no dependents means the Gitter channel and monorepo tracker are the only channels, with no service expectation.
Scaling. Horizontal scaling is inherited from the underlying driver — Redis/Memcached scale as they always do; the adapter adds no shared state of its own. Vertical impact is small (a thin delegation layer), with the main measurable cost being redundant serialization and any loop-based multi-key operations; benchmark getMultiple before using it on a hot path. Two scaling caveats are worth stating in the design review: tag-based invalidation in a multi-instance Redis deployment depends on shared tag state and can produce stale reads during races, and PSR-6 has no stampede protection, so hot-key regeneration still needs a lock strategy implemented natively outside the pool.
Failure modes.
| Scenario | Blast radius | Mitigation |
|---|---|---|
| Adapter throws on an undocumented edge case (TTL, non-serializable value) | Single consumer errors; potentially noisy if on a request path | Wrap the pool at the call site with a try/catch that degrades to a miss rather than a 500 |
| Underlying store unavailable | Pool unusable, as with native cache | Treat cache failures as non-fatal by policy; circuit-breaker or fail-open at the consumer |
| TTL or "forever" semantics mismatch | Stale data or premature misses; silent, hard to spot | Explicit conformance tests; assert expiry behaviour in the pilot |
| Tag invalidation inconsistency across instances | Stale reads in one region/instance | Integration test per driver; prefer versioned keys over tags for correctness-critical data |
| PHP/Laravel upgrade breaks the adapter | Blocked framework upgrade | Dependency scanning plus conformance tests in CI; vendor fork as escape hatch |
| Upstream goes quiet | No upstream fixes | Pre-approved fork budget; code is small enough to own |
| Team treats the pool as a full cache API | Missing locks/remember reimplemented badly |
ADR plus runbook stating the boundary explicitly |
Ramp-up. Expect 2–4 hours for a developer to integrate and test one consumer, contingent on the conformance record already existing — that record is what turns this from a half-day of exploratory debugging into a 30-minute task. Prerequisites are basic: Laravel cache configuration and enough PSR-6 literacy to know that a CacheItem is a lazy value object and that save() on an unmodified item should be a no-op. If the team has no PSR-6 exposure at all, add a 45-minute walkthrough to onboarding; the concepts are simple, but the miss-path and immutability semantics are where people get it wrong.
Recommendation. Adopt conditionally: proceed only if a concrete PSR-6 consumer exists and the conformance check clears on our production driver and versions. Scope it as an interoperability shim for third-party consumers, not as the application's caching standard — keep first-party code on native Laravel cache. The decision cost is a few engineer-days; the value is avoiding a parallel cache stack. The residual risk is ownership ambiguity, which is fully mitigable by assigning a named maintainer and pre-approving the fork.
How can I help you explore Laravel packages today?