Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit96667d3

Browse files
committed
feature#46880 [HttpKernel] Add#[Cache()] to describe the default HTTP cache headers on controllers (nicolas-grekas)
This PR was merged into the 6.2 branch.Discussion----------[HttpKernel] Add `#[Cache()]` to describe the default HTTP cache headers on controllers| Q | A| ------------- | ---| Branch? | 6.2| Bug fix? | no| New feature? | yes| Deprecations? | no| Tickets | Part of#44705| License | MIT| Doc PR | -Extracted from#45415 (and modernized a lot).I'd appreciate any help for porting the other attributes following this leading PR 🙏Commits-------acd4fa7 [HttpKernel] Add `#[Cache]` to describe the default HTTP cache headers on controllers
2 parents338daf2 +acd4fa7 commit96667d3

File tree

8 files changed

+650
-3
lines changed

8 files changed

+650
-3
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
useSymfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
2525
useSymfony\Component\HttpKernel\Controller\ErrorController;
2626
useSymfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
27+
useSymfony\Component\HttpKernel\EventListener\CacheAttributeListener;
2728
useSymfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener;
2829
useSymfony\Component\HttpKernel\EventListener\ErrorListener;
2930
useSymfony\Component\HttpKernel\EventListener\LocaleListener;
@@ -117,5 +118,9 @@
117118
])
118119
->tag('kernel.event_subscriber')
119120
->tag('monolog.logger', ['channel' =>'request'])
121+
122+
->set('controller.cache_attribute_listener', CacheAttributeListener::class)
123+
->tag('kernel.event_subscriber')
124+
120125
;
121126
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\HttpKernel\Attribute;
13+
14+
/**
15+
* Describes the default HTTP cache headers on controllers.
16+
*
17+
* @author Fabien Potencier <fabien@symfony.com>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
20+
finalclass Cache
21+
{
22+
publicfunction__construct(
23+
/**
24+
* The expiration date as a valid date for the strtotime() function.
25+
*/
26+
public ?string$expires =null,
27+
28+
/**
29+
* The number of seconds that the response is considered fresh by a private
30+
* cache like a web browser.
31+
*/
32+
publicint|string|null$maxage =null,
33+
34+
/**
35+
* The number of seconds that the response is considered fresh by a public
36+
* cache like a reverse proxy cache.
37+
*/
38+
publicint|string|null$smaxage =null,
39+
40+
/**
41+
* Whether the response is public or not.
42+
*/
43+
public ?bool$public =null,
44+
45+
/**
46+
* Whether or not the response must be revalidated.
47+
*/
48+
publicbool$mustRevalidate =false,
49+
50+
/**
51+
* Additional "Vary:"-headers.
52+
*/
53+
publicarray$vary = [],
54+
55+
/**
56+
* An expression to compute the Last-Modified HTTP header.
57+
*/
58+
public ?string$lastModified =null,
59+
60+
/**
61+
* An expression to compute the ETag HTTP header.
62+
*/
63+
public ?string$etag =null,
64+
65+
/**
66+
* max-stale Cache-Control header
67+
* It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...).
68+
*/
69+
publicint|string|null$maxStale =null,
70+
71+
/**
72+
* stale-while-revalidate Cache-Control header
73+
* It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...).
74+
*/
75+
publicint|string|null$staleWhileRevalidate =null,
76+
77+
/**
78+
* stale-if-error Cache-Control header
79+
* It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...).
80+
*/
81+
publicint|string|null$staleIfError =null,
82+
) {
83+
}
84+
}

‎src/Symfony/Component/HttpKernel/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add constructor argument`bool $catchThrowable` to`HttpKernel`
88
* Add`ControllerEvent::getAttributes()` to handle attributes on controllers
9+
* Add`#[Cache]` to describe the default HTTP cache headers on controllers
910

1011
6.1
1112
---

‎src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
2121
/**
2222
* {@inheritdoc}
2323
*/
24-
publicfunctioncreateArgumentMetadata(string|object|array$controller,\ReflectionClass$class =null,\ReflectionFunction$reflection =null):array
24+
publicfunctioncreateArgumentMetadata(string|object|array$controller,\ReflectionClass$class =null,\ReflectionFunctionAbstract$reflection =null):array
2525
{
2626
$arguments = [];
2727

‎src/Symfony/Component/HttpKernel/Event/ControllerEvent.php‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,18 @@ public function setController(callable $controller, array $attributes = null): v
6161
unset($this->attributes);
6262
}
6363

64-
$action =new \ReflectionFunction($controller(...));
65-
$this->getRequest()->attributes->set('_controller_reflectors', [str_contains($action->name,'{closure}') ?null :$action->getClosureScopeClass(),$action]);
64+
if (\is_array($controller) &&method_exists(...$controller)) {
65+
$action =new \ReflectionMethod(...$controller);
66+
$class =new \ReflectionClass($controller[0]);
67+
}elseif (\is_string($controller) &&false !==$i =strpos($controller,'::')) {
68+
$action =new \ReflectionMethod($controller);
69+
$class =new \ReflectionClass(substr($controller,0,$i));
70+
}else {
71+
$action =new \ReflectionFunction($controller(...));
72+
$class =str_contains($action->name,'{closure}') ?null :$action->getClosureScopeClass();
73+
}
74+
75+
$this->getRequest()->attributes->set('_controller_reflectors', [$class,$action]);
6676
$this->controller =$controller;
6777
}
6878

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\HttpKernel\EventListener;
13+
14+
useSymfony\Component\EventDispatcher\EventSubscriberInterface;
15+
useSymfony\Component\ExpressionLanguage\ExpressionLanguage;
16+
useSymfony\Component\HttpFoundation\Request;
17+
useSymfony\Component\HttpFoundation\Response;
18+
useSymfony\Component\HttpKernel\Attribute\Cache;
19+
useSymfony\Component\HttpKernel\Event\ControllerEvent;
20+
useSymfony\Component\HttpKernel\Event\ResponseEvent;
21+
useSymfony\Component\HttpKernel\KernelEvents;
22+
23+
/**
24+
* Handles HTTP cache headers configured via the Cache attribute.
25+
*
26+
* @author Fabien Potencier <fabien@symfony.com>
27+
*/
28+
class CacheAttributeListenerimplements EventSubscriberInterface
29+
{
30+
/**
31+
* @var \SplObjectStorage<Request, \DateTimeInterface>
32+
*/
33+
private\SplObjectStorage$lastModified;
34+
35+
/**
36+
* @var \SplObjectStorage<Request, string>
37+
*/
38+
private\SplObjectStorage$etags;
39+
40+
publicfunction__construct(
41+
private ?ExpressionLanguage$expressionLanguage =null,
42+
) {
43+
$this->lastModified =new \SplObjectStorage();
44+
$this->etags =new \SplObjectStorage();
45+
}
46+
47+
/**
48+
* Handles HTTP validation headers.
49+
*/
50+
publicfunctiononKernelController(ControllerEvent$event)
51+
{
52+
$request =$event->getRequest();
53+
54+
if (!\is_array($attributes =$request->attributes->get('_cache') ??$event->getAttributes()[Cache::class] ??null)) {
55+
return;
56+
}
57+
58+
$request->attributes->set('_cache',$attributes);
59+
$response =null;
60+
$lastModified =null;
61+
$etag =null;
62+
63+
/** @var Cache[] $attributes */
64+
foreach ($attributesas$cache) {
65+
if (null !==$cache->lastModified) {
66+
$lastModified =$this->getExpressionLanguage()->evaluate($cache->lastModified,$request->attributes->all());
67+
($response ??=newResponse())->setLastModified($lastModified);
68+
}
69+
70+
if (null !==$cache->etag) {
71+
$etag =hash('sha256',$this->getExpressionLanguage()->evaluate($cache->etag,$request->attributes->all()));
72+
($response ??=newResponse())->setEtag($etag);
73+
}
74+
}
75+
76+
if ($response?->isNotModified($request)) {
77+
$event->setController(staticfn () =>$response);
78+
$event->stopPropagation();
79+
80+
return;
81+
}
82+
83+
if (null !==$etag) {
84+
$this->etags[$request] =$etag;
85+
}
86+
if (null !==$lastModified) {
87+
$this->lastModified[$request] =$lastModified;
88+
}
89+
}
90+
91+
/**
92+
* Modifies the response to apply HTTP cache headers when needed.
93+
*/
94+
publicfunctiononKernelResponse(ResponseEvent$event)
95+
{
96+
$request =$event->getRequest();
97+
98+
/** @var Cache[] $attributes */
99+
if (!\is_array($attributes =$request->attributes->get('_cache'))) {
100+
return;
101+
}
102+
$response =$event->getResponse();
103+
104+
// http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
105+
if (!\in_array($response->getStatusCode(), [200,203,300,301,302,304,404,410])) {
106+
unset($this->lastModified[$request]);
107+
unset($this->etags[$request]);
108+
109+
return;
110+
}
111+
112+
if (isset($this->lastModified[$request]) && !$response->headers->has('Last-Modified')) {
113+
$response->setLastModified($this->lastModified[$request]);
114+
}
115+
116+
if (isset($this->etags[$request]) && !$response->headers->has('Etag')) {
117+
$response->setEtag($this->etags[$request]);
118+
}
119+
120+
unset($this->lastModified[$request]);
121+
unset($this->etags[$request]);
122+
$hasVary =$response->headers->has('Vary');
123+
124+
foreach (array_reverse($attributes)as$cache) {
125+
if (null !==$cache->smaxage && !$response->headers->hasCacheControlDirective('s-maxage')) {
126+
$response->setSharedMaxAge($this->toSeconds($cache->smaxage));
127+
}
128+
129+
if ($cache->mustRevalidate) {
130+
$response->headers->addCacheControlDirective('must-revalidate');
131+
}
132+
133+
if (null !==$cache->maxage && !$response->headers->hasCacheControlDirective('max-age')) {
134+
$response->setMaxAge($this->toSeconds($cache->maxage));
135+
}
136+
137+
if (null !==$cache->maxStale && !$response->headers->hasCacheControlDirective('max-stale')) {
138+
$response->headers->addCacheControlDirective('max-stale',$this->toSeconds($cache->maxStale));
139+
}
140+
141+
if (null !==$cache->staleWhileRevalidate && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) {
142+
$response->headers->addCacheControlDirective('stale-while-revalidate',$this->toSeconds($cache->staleWhileRevalidate));
143+
}
144+
145+
if (null !==$cache->staleIfError && !$response->headers->hasCacheControlDirective('stale-if-error')) {
146+
$response->headers->addCacheControlDirective('stale-if-error',$this->toSeconds($cache->staleIfError));
147+
}
148+
149+
if (null !==$cache->expires && !$response->headers->has('Expires')) {
150+
$response->setExpires(new \DateTimeImmutable('@'.strtotime($cache->expires,time())));
151+
}
152+
153+
if (!$hasVary &&$cache->vary) {
154+
$response->setVary($cache->vary,false);
155+
}
156+
}
157+
158+
foreach ($attributesas$cache) {
159+
if (true ===$cache->public) {
160+
$response->setPublic();
161+
}
162+
163+
if (false ===$cache->public) {
164+
$response->setPrivate();
165+
}
166+
}
167+
}
168+
169+
publicstaticfunctiongetSubscribedEvents():array
170+
{
171+
return [
172+
KernelEvents::CONTROLLER => ['onKernelController',10],
173+
KernelEvents::RESPONSE => ['onKernelResponse', -10],
174+
];
175+
}
176+
177+
privatefunctiongetExpressionLanguage():ExpressionLanguage
178+
{
179+
return$this->expressionLanguage ??=class_exists(ExpressionLanguage::class)
180+
?newExpressionLanguage()
181+
:thrownew \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
182+
}
183+
184+
privatefunctiontoSeconds(int|string$time):int
185+
{
186+
if (!is_numeric($time)) {
187+
$now =time();
188+
$time =strtotime($time,$now) -$now;
189+
}
190+
191+
return$time;
192+
}
193+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp