Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Responsecache Php72 Laravel Package

codeat3/laravel-responsecache-php72

A PHP 7.2-compatible fork of Laravel ResponseCache that speeds up your app by caching full responses. Store, tag, and invalidate cached pages easily, with support for middleware, cache profiles, and automatic clearing on model changes.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a namespace-preserving fork of spatie/laravel-responsecache, so treat it as a replacement, not an addition. Two packages cannot coexist because both declare Spatie\ResponseCache\....

composer remove spatie/laravel-responsecache   # must be gone first
composer require codeat3/laravel-responsecache-php72
php artisan vendor:publish --provider="Spatie\ResponseCache\ResponseCacheServiceProvider"

Register the middleware aliases in app/Http/Kernel.php ($routeMiddleware — on Laravel ≤ 6.x, which is the realistic 7.2 target, that is the array name):

'responsecache'     => \Spatie\ResponseCache\Middlewares\CacheResponse::class,
'doNotCacheResponse' => \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class,

Where to look first, in this order:

  1. config/responsecache.php — the entire behavioral surface. Read enabled, cache_profile, cache_lifetime_in_seconds (older forks: cache_lifetime_in_minutes), cache_store, cache_tag.
  2. src/CacheProfiles/DefaultCacheProfile.php — the gate every request passes through. If a route gets cached, it is because this said yes.
  3. src/Middlewares/CacheResponse.php — shows exactly where the profile is consulted, when the cache is read, and when the response is stored.
  4. src/ResponseCacheServiceProvider.php — confirms which facade methods and Artisan commands actually exist in this fork's version.

First use case: one anonymous, read-only public route — a marketing page or a public catalog index — behind an explicit route group, never the whole web group:

Route::middleware('responsecache')->group(function () {
    Route::get('/pricing', 'MarketingController@pricing');
    Route::get('/catalog', 'CatalogController@index');
});

Verify before you go further. Hit the route twice and confirm the second request is served from cache — via the package events (ResponseCached, then a hit), or via ResponseCache::hasBeenCached(...) depending on the fork's facade signature:

Route::get('/_cache-probe', function () {
    return ['cached' => ResponseCache::hasBeenCached(request()->fullUrl())];
});

Then check the real backend (redis-cli KEYS '*responsecache*') — the array store will happily report success and cache nothing across requests.

Implementation Patterns

Scope by route allowlist, not by group. Applying responsecache to web is the single most common way this package becomes a security incident. Inventory routes first, classify by anonymity / personalization / write frequency, then attach middleware per route or per narrow group.

Write a custom CacheProfile — this is your main guardrail. The default profile only checks for GET. A production profile should reject everything you cannot reason about:

namespace App\CacheProfiles;

use Illuminate\Http\Request;
use Spatie\ResponseCache\CacheProfiles\BaseCacheProfile; // or implement CacheProfile directly
use Illuminate\Support\Facades\Auth;

class PublicPagesProfile extends BaseCacheProfile
{
    public function shouldCacheRequest(Request $request): bool
    {
        if (! $request->isMethod('GET')) {
            return false;
        }
        if (Auth::check()) {
            return false;                       // never cache an authenticated view
        }
        if ($request->ajax() || $request->wantsJson()) {
            return false;
        }
        if ($request->hasAny(['preview', 'draft', 'token', 'page', 'sort', 'filter'])) {
            return false;                       // or normalize into the suffix instead
        }
        if ($request->cookie('preview') || $request->cookie('admin_preview')) {
            return false;
        }
        return true;
    }

    public function useCacheNameSuffix(Request $request): string
    {
        return implode('|', [
            app()->getLocale(),
            $request->cookie('currency', 'usd'),
            $request->header('X-Device-Class', 'desktop'),
        ]);
    }
}

(Method names differ across upstream majors — shouldCatch() in v5, shouldCacheRequest() in v6+. Check the interface in the fork you actually installed; a mismatch surfaces as a fatal error on boot, which is at least loud.)

Wire it up with 'cache_profile' => App\CacheProfiles\PublicPagesProfile::class.

Use useCacheNameSuffix() for every axis of variation — locale, currency, device class, A/B bucket, country. Anything that changes the HTML must be in the suffix or in a Vary header, otherwise users see each other's variants. Vary: Accept-Language, Cookie on the response and on the CDN in front of it; a CDN that ignores Vary will undo your work.

Opt out explicitly on every sensitive route. The allowlist handles it, but defense in depth costs one line:

Route::middleware('doNotCacheResponse')->group(function () {
    Route::get('/account', 'AccountController@show');
    Route::get('/cart', 'CartController@index');
    Route::get('/checkout', 'CheckoutController@index');
    Route::get('/preview/{page}', 'PreviewController@show');
});

Make this a checklist item in code review: any new route under web that renders user-specific data gets doNotCacheResponse or lives outside the cached group.

Invalidate on write, from one place. Model observers are the cleanest contract:

class PageObserver
{
    public function saved(Page $page)   { $this->purge($page); }
    public function deleted(Page $page) { $this->purge($page); }

    private function purge(Page $page)
    {
        ResponseCache::forget(url("/pages/{$page->slug}"));
        ResponseCache::forget(url('/pages'));
        ResponseCache::forget(url('/sitemap.xml'));
    }
}

Two invalidation styles, pick deliberately:

  • By URL (ResponseCache::forget($url)) — precise, works on any store including file. Requires you to know every URL a write affects. Brittle as the URL space grows; document the map.
  • By tag (cache_tag in config, then a tag-scoped clear) — one write purges a whole family of URLs. Requires a tag-capable store (Redis/Memcached — not file, not array). Confirm tag support in this fork's version before designing around it.

Reserve ResponseCache::clear() for deploys and content-model resets. Calling it on every save will keep the cache permanently cold and give you none of the benefit.

Handle the CSRF token explicitly. A cached page contains whatever <meta name="csrf-token"> was rendered at store time. If any cached page contains a form that posts, add a replacer so the token is swapped on the way out:

ResponseCache::replacers(array_merge(
    ResponseCache::replacers(),
    [app(App\Cache\CsrfTokenReplacer::class)]
));

Alternatively — and preferably — make the policy cached pages contain no state-changing forms at all, and let those pages be uncached.

Roll out behind a flag. enabled => env('RESPONSE_CACHE_ENABLED', false) plus ResponseCache::disable() in a request-scoped check gives you a one-switch rollback that does not require a deploy of route definitions. Pair it with your normal feature-flag mechanism if one exists.

Test it like a security boundary, not a performance optimization. Minimum suite:

public function test_public_page_is_cached()
{
    $this->get('/pricing');
    $this->get('/pricing');
    // assert via events or backend key count, not just status 200
}

public function test_authenticated_user_never_gets_a_cached_page()
{
    $this->actingAs($alice)->get('/pricing')->assertSee('[email protected]');
    $this->actingAs($bob)->get('/pricing')->assertDontSee('[email protected]');
}

public function test_flash_message_is_not_replayed_from_cache()
{
    $this->get('/pricing')->assertSessionMissing('status');
}

Run these against Redis, in a CI job that also runs the suite with the cache disabled — a passing test suite under array proves nothing.

Gotchas and Tips

The headers are cached too. This is the highest-severity pitfall. The stored response includes Set-Cookie, Vary, Cache-Control, and any per-request header. The first visitor's session cookie can be replayed to subsequent visitors. Mitigate by keeping session middleware outside the cached surface where possible, or by stripping cookies in a replacer/middleware after the cache read. Test this explicitly with curl and inspect Set-Cookie on a hit.

Middleware ordering matters more than the package does. responsecache must run where it can see a request after Auth has resolved, but its output must not be wrapped by anything that mutates the response per-user (ShareErrorsFromSession, StartSession flash handling, debugbar, locale middleware that writes cookies). Put it last in the group; verify by dumping $request->user() inside the profile.

The array store silently defeats the whole exercise. cache_store => 'array' is request-scoped; the cache is empty on every request and everything "works" while nothing is cached. Also: file store has no tag support, so tag-based invalidation becomes a no-op or an exception. Set CACHE_DRIVER=redis (or memcached) in staging and in whatever environment your tests run.

The package never adds a cache-hit header by default. Do not expect X-Cache: HIT in your terminal. Instrument via the package events (ResponseCached, ResponseCacheHit/CacheMissed in newer forks) into your logs or metrics, or add the header yourself from a small wrapper middleware. Budget an hour for this — without it you cannot compute a hit rate, and without a hit rate you cannot justify the pilot.

Missed invalidation is silent. There is no error, no alert, just stale content. Pick a TTL short enough that a missed purge self-heals (minutes-to-hours for catalogs, not a week), and add a scheduled job that purges your highest-value URLs on content-write bursts. Watch for the reverse failure too — an observer that forgets a URL, so / never updates.

Query strings are part of the cache key. ?utm_source=x fragments your cache into thousands of near-identical entries and destroys your hit rate. Either strip tracking params in the profile (shouldCacheRequest returns false for them, or normalize them out before the key is computed) or reject any request with a query string outright until you have a normalization story.

Non-200 responses and redirects. Confirm behavior on 404s, 500s, and redirects in this fork's version. Caching a transient 500 for a day is a memorable outage. If the fork does not filter by status, add a check before storing or keep TTLs short while you establish trust.

Fork drift and ownership. With 2 stars and 0 dependents, assume no upstream maintenance. Pin an exact version in composer.json ("codeat3/laravel-responsecache-php72": "1.0.3", not ^1.0), vendor it or keep a patch file, and name an owner. Before the pilot, diff the fork's CacheResponse, DefaultCacheProfile, and ResponseCache against upstream Spatie at the corresponding tag — you want to know what changed beyond the PHP 7.2 syntax backport, in particular anything touched in cache-key derivation or the Vary/header path.

Artisan command names vary. Older forks ship responsecache:clear; newer ones add responsecache:forget and/or responsecache:select. Run php artisan list | grep -i cache and grep the service provider rather than trusting README snippets — deploying a purge hook that calls a nonexistent command means your CDN and your app cache diverge on release day.

Extension points, ranked by usefulness: (1) a custom CacheProfile — covers 90% of policy needs; (2) ResponseCache::replacers() — surgical content rewriting like CSRF tokens and timestamps; (3) a thin wrapper middleware around CacheResponse that adds hit/miss headers and emits metrics; (4) the facade's forget/clear/hasBeenCached for operational scripts. Avoid subclassing CacheResponse itself unless the fork has already been vendored — you will own that fork forever.

Documentation gap = your documentation. The fork ships roughly upstream's README, which describes upstream's behavior. Anything version-sensitive (profile method signatures, config key names, artisan commands, tagged-cache support) must be verified against the installed source and written into a short internal runbook: which routes are cached, what the TTL and suffix rules are, how to purge a single URL, how to purge everything, and how to flip the kill switch. That runbook is the actual deliverable of the pilot, alongside the TTFB numbers.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle