graham-campbell/cache-plugin
A Laravel package from Graham Campbell that adds a plugin layer to cache stores, letting you attach custom behavior to cache reads, writes, and deletes without changing the underlying driver. Useful for logging, metrics, and other cross-cutting concerns.
Architecture fit. This is a peripheral-seam package, not a core component. Which seam depends on resolving an identity problem first, and that problem is the single most important thing to settle before any capacity is committed:
PluginClient decorator that persists whole HTTP responses into a PSR-6 pool), it sits at the outbound edge: queue workers calling metered vendors, scheduled syncs pulling reference data, SDK-style clients talking to maps, geocoding, enrichment, KYC, payments-metadata providers. It does not fit request-path rendering, Eloquent, or anything the Http facade is asked to do in a controller. The business framing is spend reduction and integration latency, not page speed.graham-campbell/cache (pluggable stores, configurable TTLs, key namespacing, read/write/forget), it sits inside the application as a cache abstraction layer — Laravel-native, overlapping heavily with what Cache already does, and offering comparatively little marginal value over the base package.Either way, frame it as enablement infrastructure on the roadmap. It is plumbing, measured in vendor invoices avoided and incidents deflected, and it should never appear on a customer-facing roadmap line.
Integration feasibility. Feasible, but manual in both readings, and the friction is structural rather than incidental:
Http facade is Guzzle-native while the plugin decorates a PSR-18 client, so getting it onto the facade path requires a custom client binding, a Guzzle handler-stack equivalent, or — the only option that ages well — an internal typed service that all outbound vendor calls are expected to use. Separately, Laravel's cache repository is not PSR-6, so a pool adapter (symfony/cache or equivalent) must be bound explicitly, carrying its own serialization behavior for PSR-7 bodies and streams.graham-campbell/cache, not in this plugin layer; adopting the plugin system means adopting an ecosystem to get defaults and wiring.Technical risk. Implementation risk is low — the surface is a constructor, a config array, and a handful of key generators; a competent engineer reproduces equivalent behavior in roughly 50–100 lines. Strategic risk is higher and concentrates in three places: the upstream dependency carries most of the real value, so we may be taking a dependency for defaults; zero dependents and a negligible score mean effectively no external validation of this artifact; and it is a single-maintainer package. The defensible posture is "borrow the pattern, own the code" unless a source review surfaces something non-obvious.
Key questions (each maps to a decision or an estimate, not to curiosity):
graham-campbell/cache? Resolve from composer.json and src/, not from the package blurb. Two of the supplied descriptions disagree; an estimate made before this resolves is provisional.Cache-Control: no-store/private, Vary, and conditional revalidation (ETag/Last-Modified), or is TTL the only lever? This is the difference between correct caching and a mechanism for serving stale user data.Http::fake() from existing tests?Stack fit. Strongest for standalone PHP services, queue workers, and SDK-style client classes that already speak PSR-18/HTTPlug. Weakest inside Laravel controllers and request paths that go through the Http facade, where we would be working against the framework. The pragmatic posture: adopt for one bounded outbound integration, not as a platform-wide HTTP caching layer, and promote it to a shared external-services building block only after a pilot produces numbers.
Migration path. Assume the status quo is manual Cache::remember() wrapped around HTTP calls, or no caching at all. Replace it inside a single bounded client class: construct the pool → construct the plugin → wrap the client in PluginClient → expose one small typed service that the rest of the codebase calls. Keep the old path behind a config flag for one release so hit rate, latency, and vendor spend can be compared side by side. Because the abstraction boundary is one service, removal is a one-file change — communicate that reversibility upward, since it converts a dependency-adoption decision into a time-boxed experiment.
Compatibility. Verify three things before production code: the package's PHP constraint against our runtime matrix; that the chosen pool adapter serializes PSR-7 responses and streams safely (the usual source of surprise exceptions); and that swapping the client does not decouple the test suite from the real code path. If the codebase leans on Http::fake(), budget a small test-harness rework as an explicit line item — it is easy to miss in estimation and it erodes confidence in unrelated suites.
Sequencing.
Maintenance. Low code churn, but the maintenance burden is relational, not technical: single maintainer, thin release cadence, no downstream users to surface breakage early. Pin the version explicitly, watch upstream for abandonment, and keep the fork-or-reimplement exit warm. Because the abstraction boundary is one service class, exercising that exit is cheap — that is the property to protect in code review.
Support. There is no community to escalate to. Internal ownership is implied by adoption: someone must own the wrapper, the caching policy document, and the on-call runbook entry. Treat "who answers a page at 2 a.m. when the vendor calls start failing or returning stale data" as a staffing question answered before the pilot, not after.
Scaling. The cache pool becomes a shared dependency with its own capacity profile — Redis memory growth, eviction policy, and key cardinality all become production concerns the moment multiple services share a pool. TTL choice is the primary lever on both spend and staleness, and it is a per-integration judgment, not a global default. Critically, the package caches results but does not coalesce concurrent misses, so under load a cold key produces exactly the stampede the cache was meant to prevent; pair it with request coalescing or accept burst behavior deliberately.
Failure modes, ordered by blast radius:
no-store/private or Vary are not honored; correctness risk that surfaces as confusing product bugs.Http::fake(), hiding regressions in code paths nobody suspects.Each of these argues for the same two controls: a kill switch that reverts to direct calls in one config change, and first-party metrics and tracing on hit rate and pool health — observability is not included and must be built.
Ramp-up. The code is learnable in an afternoon; the semantics are not. HTTP caching correctness — freshness, Vary, revalidation, private versus shared caches, key design — is specialist knowledge most engineers have not had to acquire, and it is where the incidents live. Budget for the caching policy document as the real onboarding artifact, plus a short internal walkthrough. Keep the number of people who can change cache keys small and require review on those changes specifically; that single constraint does more for safety than any amount of tooling.
How can I help you explore Laravel packages today?