Skip to content

Commit bf09616

Browse files
authored
feat(httpcache): add more cache directives to AddHeadersProcessor (#7008)
1 parent 7806e85 commit bf09616

File tree

4 files changed

+65
-49
lines changed

4 files changed

+65
-49
lines changed

.commitlintrc

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"mongodb",
1515
"jsonld",
1616
"hydra",
17+
"httpcache",
1718
"jsonapi",
1819
"graphql",
1920
"openapi",

src/HttpCache/State/AddHeadersProcessor.php

+18-25
Original file line numberDiff line numberDiff line change
@@ -52,38 +52,31 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
5252

5353
$resourceCacheHeaders = $operation->getCacheHeaders() ?? [];
5454

55-
if ($this->etag && !$response->getEtag()) {
56-
$response->setEtag(hash('xxh3', (string) $content));
57-
}
55+
$public = ($resourceCacheHeaders['public'] ?? $this->public);
5856

59-
if (null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age')) {
60-
$response->setMaxAge($maxAge);
61-
}
57+
$options = [
58+
'etag' => $this->etag && !$response->getEtag() ? hash('xxh3', (string) $content) : null,
59+
'max_age' => null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age') ? $maxAge : null,
60+
// Cache-Control "s-maxage" is only relevant is resource is not marked as "private"
61+
's_maxage' => false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage') ? $sharedMaxAge : null,
62+
'public' => null !== $public && !$response->headers->hasCacheControlDirective('public') ? $public : null,
63+
'stale_while_revalidate' => null !== ($staleWhileRevalidate = $resourceCacheHeaders['stale_while_revalidate'] ?? $this->staleWhileRevalidate) && !$response->headers->hasCacheControlDirective('stale-while-revalidate') ? $staleWhileRevalidate : null,
64+
'stale_if_error' => null !== ($staleIfError = $resourceCacheHeaders['stale_if_error'] ?? $this->staleIfError) && !$response->headers->hasCacheControlDirective('stale-if-error') ? $staleIfError : null,
65+
'must_revalidate' => null !== ($mustRevalidate = $resourceCacheHeaders['must_revalidate'] ?? null) && !$response->headers->hasCacheControlDirective('must-revalidate') ? $mustRevalidate : null,
66+
'proxy_revalidate' => null !== ($proxyRevalidate = $resourceCacheHeaders['proxy_revalidate'] ?? null) && !$response->headers->hasCacheControlDirective('proxy-revalidate') ? $proxyRevalidate : null,
67+
'no_cache' => null !== ($noCache = $resourceCacheHeaders['no_cache'] ?? null) && !$response->headers->hasCacheControlDirective('no-cache') ? $noCache : null,
68+
'no_store' => null !== ($noStore = $resourceCacheHeaders['no_store'] ?? null) && !$response->headers->hasCacheControlDirective('no-store') ? $noStore : null,
69+
'no_transform' => null !== ($noTransform = $resourceCacheHeaders['no_transform'] ?? null) && !$response->headers->hasCacheControlDirective('no-transform') ? $noTransform : null,
70+
'immutable' => null !== ($immutable = $resourceCacheHeaders['immutable'] ?? null) && !$response->headers->hasCacheControlDirective('immutable') ? $immutable : null,
71+
];
72+
73+
$response->setCache($options);
6274

6375
$vary = $resourceCacheHeaders['vary'] ?? $this->vary;
6476
if (null !== $vary) {
6577
$response->setVary(array_diff($vary, $response->getVary()), false);
6678
}
6779

68-
// if the public-property is defined and not yet set; apply it to the response
69-
$public = ($resourceCacheHeaders['public'] ?? $this->public);
70-
if (null !== $public && !$response->headers->hasCacheControlDirective('public')) {
71-
$public ? $response->setPublic() : $response->setPrivate();
72-
}
73-
74-
// Cache-Control "s-maxage" is only relevant is resource is not marked as "private"
75-
if (false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage')) {
76-
$response->setSharedMaxAge($sharedMaxAge);
77-
}
78-
79-
if (null !== ($staleWhileRevalidate = $resourceCacheHeaders['stale_while_revalidate'] ?? $this->staleWhileRevalidate) && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) {
80-
$response->headers->addCacheControlDirective('stale-while-revalidate', (string) $staleWhileRevalidate);
81-
}
82-
83-
if (null !== ($staleIfError = $resourceCacheHeaders['stale_if_error'] ?? $this->staleIfError) && !$response->headers->hasCacheControlDirective('stale-if-error')) {
84-
$response->headers->addCacheControlDirective('stale-if-error', (string) $staleIfError);
85-
}
86-
8780
return $response;
8881
}
8982
}

src/HttpCache/Tests/State/AddHeadersProcessorTest.php

+39-23
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,54 @@
1919
use PHPUnit\Framework\TestCase;
2020
use Symfony\Component\HttpFoundation\Request;
2121
use Symfony\Component\HttpFoundation\Response;
22-
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
2322

2423
class AddHeadersProcessorTest extends TestCase
2524
{
26-
public function testAddHeaders(): void
25+
public function testAddHeadersFromGlobalConfiguration(): void
2726
{
2827
$operation = new Get();
29-
$response = $this->createMock(Response::class);
30-
$response->expects($this->once())->method('setEtag');
31-
$response->method('getContent')->willReturn('{}');
32-
$response->method('isSuccessful')->willReturn(true);
33-
$response->headers = $this->createMock(ResponseHeaderBag::class);
34-
$response->headers->method('hasCacheControlDirective')->with($this->logicalOr(
35-
$this->identicalTo('public'),
36-
$this->identicalTo('s-maxage'),
37-
$this->identicalTo('max-age'),
38-
$this->identicalTo('stale-while-revalidate'),
39-
$this->identicalTo('stale-if-error'),
40-
))->willReturn(false);
41-
$response->headers->expects($this->exactly(2))->method('addCacheControlDirective')->with($this->logicalOr(
42-
$this->identicalTo('stale-while-revalidate'),
43-
$this->identicalTo('stale-if-error'),
44-
), '10');
45-
$response->expects($this->once())->method('setPublic');
46-
$response->expects($this->once())->method('setMaxAge');
47-
$response->expects($this->once())->method('setSharedMaxAge');
48-
$request = $this->createMock(Request::class);
49-
$request->method('isMethodCacheable')->willReturn(true);
28+
$response = new Response('content');
29+
$request = new Request();
5030
$context = ['request' => $request];
5131
$decorated = $this->createMock(ProcessorInterface::class);
5232
$decorated->method('process')->willReturn($response);
5333
$processor = new AddHeadersProcessor($decorated, etag: true, maxAge: 100, sharedMaxAge: 200, vary: ['Accept', 'Accept-Encoding'], public: true, staleWhileRevalidate: 10, staleIfError: 10);
34+
5435
$processor->process($response, $operation, [], $context);
36+
37+
self::assertSame('max-age=100, public, s-maxage=200, stale-if-error=10, stale-while-revalidate=10', $response->headers->get('cache-control'));
38+
self::assertSame('"55f2b31a6acfaa64"', $response->headers->get('etag'));
39+
self::assertSame(['Accept', 'Accept-Encoding'], $response->headers->all('vary'));
40+
}
41+
42+
public function testAddHeadersFromOperationConfiguration(): void
43+
{
44+
$operation = new Get(
45+
cacheHeaders: [
46+
'public' => false,
47+
'max_age' => 250,
48+
'shared_max_age' => 500,
49+
'stale_while_revalidate' => 30,
50+
'stale_if_error' => 15,
51+
'vary' => ['Authorization', 'Accept-Language'],
52+
'must_revalidate' => true,
53+
'proxy_revalidate' => true,
54+
'no_cache' => true,
55+
'no_store' => true,
56+
'no_transform' => true,
57+
'immutable' => true,
58+
],
59+
);
60+
$response = new Response('content');
61+
$request = new Request();
62+
$context = ['request' => $request];
63+
$decorated = $this->createMock(ProcessorInterface::class);
64+
$decorated->method('process')->willReturn($response);
65+
$processor = new AddHeadersProcessor($decorated);
66+
67+
$processor->process($response, $operation, [], $context);
68+
69+
self::assertSame('immutable, max-age=250, must-revalidate, no-store, no-transform, private, proxy-revalidate, stale-if-error=15, stale-while-revalidate=30', $response->headers->get('cache-control'));
70+
self::assertSame(['Authorization', 'Accept-Language'], $response->headers->all('vary'));
5571
}
5672
}

src/Metadata/HttpOperation.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ class HttpOperation extends Operation
5555
* public?: bool,
5656
* shared_max_age?: int,
5757
* stale_while_revalidate?: int,
58-
* stale-if-error?: int,
58+
* stale_if_error?: int,
59+
* must_revalidate?: bool,
60+
* proxy_revalidate?: bool,
61+
* no_cache?: bool,
62+
* no_store?: bool,
63+
* no_transform?: bool,
64+
* immutable?: bool,
5965
* }|null $cacheHeaders {@see https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers}
6066
* @param array<string, string>|null $headers
6167
* @param array{

0 commit comments

Comments
 (0)