Skip to content

Commit 7f5be36

Browse files
eguiluzDavid Eguiluz Lópezfreekmurzeclaude
authored
Fix: defer cache storage to terminate() to include post-response modifications (#513)
* Defer cache storage to terminate() to include post-response modifications * Remove comments from changed code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: David Eguiluz López <davidel@raiolanetworks.es> Co-authored-by: Freek Van der Herten <freek@spatie.be> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ca50f06 commit 7f5be36

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

src/Middlewares/CacheResponse.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919

2020
class CacheResponse extends BaseCacheMiddleware
2121
{
22+
private bool $shouldCache = false;
23+
24+
private ?int $pendingLifetime = null;
25+
26+
/** @var string[] */
27+
private array $pendingTags = [];
28+
2229
public function __construct(
2330
protected ResponseCache $responseCache,
2431
) {}
@@ -41,6 +48,10 @@ public static function for(
4148

4249
public function handle(Request $request, Closure $next, ...$args): Response
4350
{
51+
$this->shouldCache = false;
52+
$this->pendingLifetime = null;
53+
$this->pendingTags = [];
54+
4455
$attribute = $this->getAttributeFromRequest($request);
4556

4657
if ($attribute instanceof NoCache) {
@@ -69,7 +80,9 @@ public function handle(Request $request, Closure $next, ...$args): Response
6980
$response = $next($request);
7081

7182
if ($this->responseCache->shouldCache($request, $response)) {
72-
$this->cacheResponse($request, $response, $lifetimeInSeconds, $tags);
83+
$this->shouldCache = true;
84+
$this->pendingLifetime = $lifetimeInSeconds;
85+
$this->pendingTags = $tags;
7386
}
7487

7588
$cacheKey = app(RequestHasher::class)->getHashFor($request);
@@ -80,6 +93,15 @@ public function handle(Request $request, Closure $next, ...$args): Response
8093
return $response;
8194
}
8295

96+
public function terminate(Request $request, Response $response): void
97+
{
98+
if (! $this->shouldCache) {
99+
return;
100+
}
101+
102+
$this->cacheResponse($request, $response, $this->pendingLifetime, $this->pendingTags);
103+
}
104+
83105
protected function getCachedResponse(Request $request, array $tags): ?Response
84106
{
85107
if (! $this->responseCache->hasBeenCached($request, $tags)) {
@@ -115,6 +137,8 @@ protected function cacheResponse(
115137
): void {
116138
$cachedResponse = clone $response;
117139

140+
$cachedResponse->headers->remove(config('responsecache.debug.cache_status_header_name'));
141+
118142
$this->addCacheTimeHeader($cachedResponse);
119143

120144
$this->getReplacers()->each(fn (Replacer $replacer) => $replacer->prepareResponseToCache($cachedResponse));

src/ResponseCacheServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Spatie\ResponseCache\CacheProfiles\CacheProfile;
1010
use Spatie\ResponseCache\Commands\ClearCommand;
1111
use Spatie\ResponseCache\Hasher\RequestHasher;
12+
use Spatie\ResponseCache\Middlewares\CacheResponse;
1213
use Spatie\ResponseCache\Serializers\Serializer;
1314

1415
class ResponseCacheServiceProvider extends PackageServiceProvider
@@ -25,6 +26,8 @@ public function configurePackage(Package $package): void
2526

2627
public function packageBooted()
2728
{
29+
$this->app->singleton(CacheResponse::class);
30+
2831
$this->app->bind(CacheProfile::class, function (Container $app) {
2932
return $app->make(config('responsecache.cache_profile'));
3033
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
use Illuminate\Foundation\Http\Events\RequestHandled;
4+
use Illuminate\Support\Facades\Route;
5+
use Spatie\ResponseCache\Middlewares\CacheResponse;
6+
7+
it('includes modifications made by RequestHandled listeners in the cached response', function () {
8+
$rendered = false;
9+
10+
Route::get('/with-listener', function () use (&$rendered) {
11+
$rendered = true;
12+
13+
return '<html><head></head><body>page content</body></html>';
14+
})->middleware(CacheResponse::class);
15+
16+
app('events')->listen(RequestHandled::class, function ($handled) use (&$rendered) {
17+
if (! $rendered) {
18+
return;
19+
}
20+
21+
$rendered = false;
22+
23+
$content = $handled->response->getContent();
24+
$handled->response->setContent(
25+
str_replace('</head>', '<script src="livewire.js"></script></head>', $content)
26+
);
27+
});
28+
29+
$firstResponse = $this->get('/with-listener');
30+
assertRegularResponse($firstResponse);
31+
expect($firstResponse->getContent())->toContain('<script src="livewire.js"></script>');
32+
33+
$secondResponse = $this->get('/with-listener');
34+
assertCachedResponse($secondResponse);
35+
expect($secondResponse->getContent())->toContain('<script src="livewire.js"></script>');
36+
37+
assertSameResponse($firstResponse, $secondResponse);
38+
});

0 commit comments

Comments
 (0)