philipannis/laravel-http-remember
A small Laravel package that brings Cache::remember-style caching to the HTTP client. Cache outgoing API responses per request with a configurable TTL and cache store, then serve repeated calls from cache to reduce latency, duplicate requests, and load on rate-limited third-party APIs.
composer require philipannis/laravel-http-remember and publish the config (php artisan vendor:publish --tag=... — confirm the tag in the README).config/http-remember.php before writing any code. It now covers conditional caching, ignored headers, size limits, event logging, manual invalidation, miss deduplication, refresh cooldown/retry-after, and log redaction.ignored_headers entries — see Gotchas.First use case: cache an expensive outgoing HTTP call (or a controller response) that is identical for many users — e.g. a third-party currency rates or catalog endpoint — and invalidate it as a group when the upstream data changes. Start with a single remember-style call, verify a cache hit on the second request, then layer in groups and conditions.
If you're evaluating the package, confirm three things first: which cache store you'll use (group invalidation still depends on it), whether you need per-user or shared caching, and whether your expected traffic has thundering-herd characteristics (in which case enable deduplication from day one).
Basic caching. Wrap the call in the package's remember API, supplying a key, TTL, and a closure that performs the request. Keep the closure pure — it may be skipped entirely on a hit, so don't put side effects (logging, counters, model writes) inside it.
Group invalidation. Assign each cached entry to one or more logical groups (e.g. products, tenant:42) and flush a group when the underlying data changes. Standardize on this: observers/model events call the group flush, and HTTP-layer code never worries about individual keys. Design group names as stable, low-cardinality strings.
Manual invalidation (v1.0.3). For cases where a whole group is too blunt — one product changed, one tenant's settings were edited — use the new manual invalidation API to forget specific keys. Typical pattern:
Cache miss deduplication (v1.0.3). When a hot key expires, all concurrent requests would otherwise stampede the upstream. Enable deduplication so only one request refreshes while others wait for or reuse the result. This is essentially mandatory for expensive upstreams under load; combine with a short lock TTL so a hung upstream doesn't block forever. Watch for the failure mode where the "winning" request errors and everyone behind the lock gets the error — make sure your fallback path is well-tested.
Refresh cooldown and retry-after (v1.0.3). Use the cooldown to prevent refresh storms (a burst of misses triggering repeated upstream calls) and to respect upstream rate limits. Have your closure surface Retry-After so the package can back off the right amount; then verify behaviour when the upstream returns 429/503 by asserting on events, not just on the cache contents.
Conditional caching. Use the conditional hooks to decide at runtime whether a response is cacheable:
Keep the condition cheap and deterministic; it runs on every request.
Ignored cache key headers. Normalize the cache key by excluding headers that don't affect the response body (request IDs, trace headers, User-Agent if you don't vary on it). This dramatically improves hit rates with noisy clients. v1.0.3 adds rejection of unsafe exclusions (see Gotchas) — the guardrails now protect you, but be prepared for config-load failures during upgrade.
Response size limit. Set a ceiling so a single huge upstream payload can't blow up the cache store or evict everything else. Treat the limit as a guardrail, not a tuning knob — if you're consistently near it, the endpoint probably shouldn't be cached wholesale.
Log redaction (v1.0.3). Enable redaction for anything sensitive that appears in lifecycle logs (auth headers, query-string tokens, PII in URLs). If you have custom event listeners that log the full request, redaction won't save you — audit those too.
Event logs. Use lifecycle events (hit/miss/store/skip/refresh/dedup) for:
Integration tips.
array cache driver and Cache::flush() between tests; assert hit/miss via events.Unsafe header exclusions are now rejected (v1.0.3). PR #22 introduces validation of ignored_headers. The upside: the previous "footgun in reverse" is largely closed off. The downside: a config listing a header the package now considers unsafe (typically Authorization, Cookie, Accept, Accept-Language, Vary-relevant headers) may throw at config load or be dropped with a warning. Re-test the app immediately after upgrading; check the README for the exact list and the exact failure mode (exception vs. silently ignored). This is the most likely source of upgrade pain.
Group invalidation still requires a taggable cache store. Laravel's file and database stores do not support cache tags. If group invalidation is implemented via tags, it will silently degrade or throw depending on the store. Use Redis or Memcached in production, and make sure your test environment matches — an array store supports tags, so tests can pass while file fails in staging. Manual invalidation (v1.0.3) works on any store, but only if you know the exact key.
Silent skips. Conditional caching and the response size limit both cause entries to be not cached, without an error. Events are your only visibility. In production, count skips per reason and alert if the skip rate spikes — it usually means an upstream change (bigger payloads, new error shape).
Deduplication can turn a small upstream error into a big outage. If the lock holder fails, every waiter may inherit the failure. Add a fallback path (serve stale, or a short TTL fallback to a known-good value) and test the lock-holder-fails case explicitly.
Cooldown vs. correctness. A refresh cooldown means a key may keep serving a stale value after an invalidation, if the invalidation triggers a refresh that is throttled. For writes with a strong consistency requirement, combine manual invalidation with an explicit forget rather than relying on refresh.
Cache key collisions. If keys are derived from URLs/methods only, two different auth contexts can collide. Always include the dimensions you vary on, and verify by testing with two different users.
TTL vs. invalidation. Prefer short TTLs plus explicit group flushes over long TTLs alone, especially for data with a known write path. Long TTLs turn a missed invalidation into a stale-data incident.
Event log volume and log content. Lifecycle events fire on every request; in high-traffic apps, log listeners can become a bottleneck. Gate verbose logging behind a config flag or sample it, and push metrics to your APM rather than the default log channel. Redaction matters here — if you log at high volume and forget to redact, you're leaking credentials to disk on every request.
Extension points to know. Look for the custom key resolver and the event classes; these are the cleanest seams for app-specific behaviour (custom key strategies, custom metrics, custom invalidation endpoints). Avoid forking the package for these; both are meant to be overridden.
Upgrade note (1.0.2 → 1.0.3). Additive features, but two of them change runtime behaviour: unsafe header exclusion rejection can break existing configs, and deduplication/cooldown changes the timing of upstream requests. No explicit breaking-change marker in the changelog, but treat it as a "re-test your caching config" release. Diff your published config, check the safe/allowed header list in the README, and run one happy-path and one failure-path test against deduplication and retry-after before rolling out.
How can I help you explore Laravel packages today?