Skip to content

Commit 32719c9

Browse files
committed
Add rule matchers for cacheable and must invalidate
1 parent 6bafbf8 commit 32719c9

18 files changed

+344
-84
lines changed

src/DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,31 +165,30 @@ private function parseRuleMatcher(ContainerBuilder $container, array $match)
165165
$match['attributes']
166166
);
167167

168-
$extraCriteria = [];
169-
foreach (['additional_cacheable_status', 'match_response'] as $extra) {
170-
if (isset($match[$extra])) {
171-
$extraCriteria[$extra] = $match[$extra];
172-
}
173-
}
168+
// $extraCriteria = [];
169+
// foreach (['additional_cacheable_status', 'match_response'] as $extra) {
170+
// if (isset($match[$extra])) {
171+
// $extraCriteria[$extra] = $match[$extra];
172+
// }
173+
// }
174174

175175
return $this->createRuleMatcher(
176176
$container,
177-
$requestMatcher,
178-
$extraCriteria
177+
$requestMatcher
179178
);
180179
}
181180

182-
private function createRuleMatcher(ContainerBuilder $container, Reference $requestMatcher, array $extraCriteria)
181+
private function createRuleMatcher(ContainerBuilder $container, Reference $requestMatcher)
183182
{
184-
$arguments = [(string) $requestMatcher, $extraCriteria];
183+
$arguments = [(string) $requestMatcher];
185184
$serialized = serialize($arguments);
186185
$id = $this->getAlias().'.rule_matcher.'.md5($serialized).sha1($serialized);
187186

188187
if (!$container->hasDefinition($id)) {
189188
$container
190189
->setDefinition($id, new DefinitionDecorator($this->getAlias().'.rule_matcher'))
191190
->replaceArgument(0, $requestMatcher)
192-
->replaceArgument(1, $extraCriteria)
191+
->replaceArgument(1, null)
193192
;
194193
}
195194

src/EventListener/InvalidationListener.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use FOS\HttpCacheBundle\CacheManager;
1616
use FOS\HttpCacheBundle\Configuration\InvalidatePath;
1717
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
18+
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
1819
use Symfony\Component\Console\ConsoleEvents;
1920
use Symfony\Component\Console\Event\ConsoleEvent;
2021
use Symfony\Component\Console\Output\OutputInterface;
@@ -55,6 +56,11 @@ class InvalidationListener extends AbstractRuleListener implements EventSubscrib
5556
*/
5657
private $expressionLanguage;
5758

59+
/**
60+
* @var RuleMatcherInterface
61+
*/
62+
private $mustInvalidateRule;
63+
5864
/**
5965
* Constructor.
6066
*
@@ -65,11 +71,13 @@ class InvalidationListener extends AbstractRuleListener implements EventSubscrib
6571
public function __construct(
6672
CacheManager $cacheManager,
6773
UrlGeneratorInterface $urlGenerator,
74+
RuleMatcherInterface $mustInvalidateRule,
6875
ExpressionLanguage $expressionLanguage = null
6976
) {
7077
$this->cacheManager = $cacheManager;
7178
$this->urlGenerator = $urlGenerator;
7279
$this->expressionLanguage = $expressionLanguage ?: new ExpressionLanguage();
80+
$this->mustInvalidateRule = $mustInvalidateRule;
7381
}
7482

7583
/**
@@ -89,9 +97,7 @@ public function onKernelTerminate(PostResponseEvent $event)
8997
$response = $event->getResponse();
9098

9199
// Don't invalidate any caches if the request was unsuccessful
92-
if ($response->getStatusCode() >= 200
93-
&& $response->getStatusCode() < 400
94-
) {
100+
if ($this->mustInvalidateRule->matches($request, $response)) {
95101
$this->handleInvalidation($request, $response);
96102
}
97103

src/EventListener/TagListener.php

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCacheBundle\CacheManager;
1515
use FOS\HttpCacheBundle\Configuration\Tag;
16+
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
1617
use FOS\HttpCacheBundle\Http\SymfonyResponseTagger;
1718
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1819
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
@@ -43,6 +44,16 @@ class TagListener extends AbstractRuleListener implements EventSubscriberInterfa
4344
*/
4445
private $expressionLanguage;
4546

47+
/**
48+
* @var RuleMatcherInterface
49+
*/
50+
private $mustInvalidateRule;
51+
52+
/**
53+
* @var RuleMatcherInterface
54+
*/
55+
private $cacheableRule;
56+
4657
/**
4758
* Constructor.
4859
*
@@ -53,10 +64,14 @@ class TagListener extends AbstractRuleListener implements EventSubscriberInterfa
5364
public function __construct(
5465
CacheManager $cacheManager,
5566
SymfonyResponseTagger $tagHandler,
67+
RuleMatcherInterface $cacheableRule,
68+
RuleMatcherInterface $mustInvalidateRule,
5669
ExpressionLanguage $expressionLanguage = null
5770
) {
5871
$this->cacheManager = $cacheManager;
5972
$this->symfonyResponseTagger = $tagHandler;
73+
$this->cacheableRule = $cacheableRule;
74+
$this->mustInvalidateRule = $mustInvalidateRule;
6075
$this->expressionLanguage = $expressionLanguage ?: new ExpressionLanguage();
6176
}
6277

@@ -74,12 +89,14 @@ public function onKernelResponse(FilterResponseEvent $event)
7489
$request = $event->getRequest();
7590
$response = $event->getResponse();
7691

77-
$tags = [];
78-
// Only set cache tags or invalidate them if response is successful
79-
if ($response->isSuccessful()) {
80-
$tags = $this->getAnnotationTags($request);
92+
if (!$this->cacheableRule->matches($request, $response)
93+
&& !$this->mustInvalidateRule->matches($request, $response)
94+
) {
95+
return;
8196
}
8297

98+
$tags = $this->getAnnotationTags($request);
99+
83100
$configuredTags = $this->matchRule($request, $response);
84101
if ($configuredTags) {
85102
$tags = array_merge($tags, $configuredTags['tags']);
@@ -88,14 +105,15 @@ public function onKernelResponse(FilterResponseEvent $event)
88105
}
89106
}
90107

91-
if ($request->isMethodCacheable()) {
108+
if ($this->cacheableRule->matches($request, $response)) {
109+
// For safe requests (GET and HEAD), set cache tags on response
92110
$this->symfonyResponseTagger->addTags($tags);
93111
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
94-
// For safe requests (GET and HEAD), set cache tags on response
95112
$this->symfonyResponseTagger->tagSymfonyResponse($response);
96113
}
97-
} elseif (count($tags)) {
98-
// For non-safe methods, invalidate the tags
114+
} elseif (count($tags)
115+
&& $this->mustInvalidateRule->matches($request, $response)
116+
) {
99117
$this->cacheManager->invalidateTags($tags);
100118
}
101119
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
/**
18+
* @see https://tools.ietf.org/html/rfc7231#section-4.2.3
19+
*/
20+
class CacheableRequestMatcher implements RequestMatcherInterface
21+
{
22+
public function matches(Request $request)
23+
{
24+
return $request->isMethodCacheable();
25+
}
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
class UnsafeRequestMatcher implements RequestMatcherInterface
18+
{
19+
public function matches(Request $request)
20+
{
21+
return !$request->isMethodSafe();
22+
}
23+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\ResponseMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
/**
17+
* Matches status codes as defined by IETF RFC 7231.
18+
*
19+
* @see https://tools.ietf.org/html/rfc7231#section-6.1
20+
*/
21+
class CacheableResponseMatcher implements ResponseMatcherInterface
22+
{
23+
private $cacheableStatusCodes = [
24+
200, 203, 204, 206,
25+
300, 301,
26+
404, 405, 410, 414,
27+
501,
28+
];
29+
30+
private $additionalStatusCodes;
31+
32+
public function __construct(array $additionalStatusCodes = [])
33+
{
34+
$this->additionalStatusCodes = $additionalStatusCodes;
35+
}
36+
37+
public function matches(Response $response)
38+
{
39+
return in_array($response->getStatusCode(), $this->cacheableStatusCodes)
40+
|| in_array($response->getStatusCode(), $this->additionalStatusCodes);
41+
}
42+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\ResponseMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
/**
17+
* "A cache MUST invalidate the effective Request URI ... when a non-error
18+
* status code is received in response to an unsafe request method".
19+
*
20+
* @see https://tools.ietf.org/html/rfc7234#section-4.4
21+
*/
22+
class NonErrorResponseMatcher implements ResponseMatcherInterface
23+
{
24+
public function matches(Response $response)
25+
{
26+
return $response->getStatusCode() >= 200
27+
&& $response->getStatusCode() < 400;
28+
}
29+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\ResponseMatcher;
13+
14+
use Symfony\Component\DependencyInjection\ExpressionLanguage;
15+
use Symfony\Component\HttpFoundation\Response;
16+
17+
class ResponseMatcher implements ResponseMatcherInterface
18+
{
19+
private $statusCodes;
20+
private $expression;
21+
private $expressionLanguage;
22+
23+
public function matchExpression($expression)
24+
{
25+
$this->expression = $expression;
26+
}
27+
28+
public function matchStatusCodes(array $statusCodes)
29+
{
30+
$this->statusCodes = $statusCodes;
31+
}
32+
33+
public function matches(Response $response)
34+
{
35+
if ($this->expression
36+
&& !$this->getExpressionLanguage()->evaluate($this->expression, [
37+
'response' => $response,
38+
])
39+
) {
40+
return false;
41+
}
42+
43+
if ($this->statusCodes
44+
&& !in_array($response->getStatusCode(), $this->statusCodes)
45+
) {
46+
return false;
47+
}
48+
49+
return true;
50+
}
51+
52+
/**
53+
* @return ExpressionLanguage
54+
*/
55+
private function getExpressionLanguage()
56+
{
57+
if (!$this->expressionLanguage) {
58+
$this->expressionLanguage = new ExpressionLanguage();
59+
}
60+
61+
return $this->expressionLanguage;
62+
}
63+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCacheBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.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+
namespace FOS\HttpCacheBundle\Http\ResponseMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
interface ResponseMatcherInterface
17+
{
18+
public function matches(Response $response);
19+
}

0 commit comments

Comments
 (0)