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\EventListener; |
13
|
|
|
|
14
|
|
|
use FOS\HttpCacheBundle\CacheManager; |
15
|
|
|
use FOS\HttpCacheBundle\Configuration\Tag; |
16
|
|
|
use FOS\HttpCacheBundle\Http\RuleMatcherInterface; |
17
|
|
|
use FOS\HttpCacheBundle\Http\SymfonyResponseTagger; |
18
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
19
|
|
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage; |
20
|
|
|
use Symfony\Component\HttpFoundation\Request; |
21
|
|
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; |
22
|
|
|
use Symfony\Component\HttpKernel\Event\ResponseEvent; |
23
|
|
|
use Symfony\Component\HttpKernel\HttpKernelInterface; |
24
|
|
|
use Symfony\Component\HttpKernel\Kernel; |
25
|
|
|
use Symfony\Component\HttpKernel\KernelEvents; |
26
|
|
|
|
27
|
1 |
View Code Duplication |
if (Kernel::MAJOR_VERSION >= 5) { |
|
|
|
|
28
|
1 |
|
class_alias(ResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\TagResponseEvent'); |
29
|
|
|
} else { |
30
|
|
|
class_alias(FilterResponseEvent::class, 'FOS\HttpCacheBundle\EventListener\TagResponseEvent'); |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Event handler for the cache tagging tags. |
35
|
|
|
* |
36
|
|
|
* @author David de Boer <[email protected]> |
37
|
|
|
*/ |
38
|
|
|
class TagListener extends AbstractRuleListener implements EventSubscriberInterface |
39
|
|
|
{ |
40
|
|
|
/** |
41
|
|
|
* @var CacheManager |
42
|
|
|
*/ |
43
|
|
|
private $cacheManager; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var SymfonyResponseTagger |
47
|
|
|
*/ |
48
|
|
|
private $symfonyResponseTagger; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var ExpressionLanguage|null |
52
|
|
|
*/ |
53
|
|
|
private $expressionLanguage; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var RuleMatcherInterface |
57
|
|
|
*/ |
58
|
|
|
private $mustInvalidateRule; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var RuleMatcherInterface |
62
|
|
|
*/ |
63
|
|
|
private $cacheableRule; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Constructor. |
67
|
|
|
*/ |
68
|
|
View Code Duplication |
public function __construct( |
|
|
|
|
69
|
|
|
CacheManager $cacheManager, |
70
|
|
|
SymfonyResponseTagger $tagHandler, |
71
|
|
|
RuleMatcherInterface $cacheableRule, |
72
|
29 |
|
RuleMatcherInterface $mustInvalidateRule, |
73
|
|
|
ExpressionLanguage $expressionLanguage = null |
74
|
|
|
) { |
75
|
|
|
$this->cacheManager = $cacheManager; |
76
|
|
|
$this->symfonyResponseTagger = $tagHandler; |
77
|
|
|
$this->cacheableRule = $cacheableRule; |
78
|
|
|
$this->mustInvalidateRule = $mustInvalidateRule; |
79
|
29 |
|
$this->expressionLanguage = $expressionLanguage; |
80
|
29 |
|
} |
81
|
29 |
|
|
82
|
29 |
|
/** |
83
|
29 |
|
* Process the _tags request attribute, which is set when using the Tag |
84
|
29 |
|
* annotation. |
85
|
|
|
* |
86
|
|
|
* - For a safe (GET or HEAD) request, the tags are set on the response. |
87
|
|
|
* - For a non-safe request, the tags will be invalidated. |
88
|
|
|
*/ |
89
|
|
|
public function onKernelResponse(TagResponseEvent $event) |
90
|
|
|
{ |
91
|
|
|
$request = $event->getRequest(); |
92
|
|
|
$response = $event->getResponse(); |
93
|
28 |
|
|
94
|
|
|
if (!$this->cacheableRule->matches($request, $response) |
95
|
28 |
|
&& !$this->mustInvalidateRule->matches($request, $response) |
96
|
28 |
|
) { |
97
|
|
|
return; |
98
|
28 |
|
} |
99
|
28 |
|
|
100
|
|
|
$tags = $this->getAnnotationTags($request); |
101
|
5 |
|
|
102
|
|
|
$configuredTags = $this->matchRule($request); |
103
|
|
|
if ($configuredTags) { |
104
|
24 |
|
$tags = array_merge($tags, $configuredTags['tags']); |
105
|
|
|
foreach ($configuredTags['expressions'] as $expression) { |
106
|
24 |
|
$tags[] = $this->evaluateTag($expression, $request); |
107
|
24 |
|
} |
108
|
5 |
|
} |
109
|
5 |
|
|
110
|
5 |
|
if ($this->cacheableRule->matches($request, $response)) { |
111
|
|
|
// For safe requests (GET and HEAD), set cache tags on response |
112
|
|
|
$this->symfonyResponseTagger->addTags($tags); |
113
|
|
|
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { |
114
|
24 |
|
$this->symfonyResponseTagger->tagSymfonyResponse($response); |
115
|
|
|
} |
116
|
16 |
|
} elseif (count($tags) |
117
|
16 |
|
&& $this->mustInvalidateRule->matches($request, $response) |
118
|
16 |
|
) { |
119
|
|
|
$this->cacheManager->invalidateTags($tags); |
120
|
8 |
|
} |
121
|
8 |
|
} |
122
|
|
|
|
123
|
4 |
|
/** |
124
|
|
|
* {@inheritdoc} |
125
|
24 |
|
*/ |
126
|
|
|
public static function getSubscribedEvents() |
127
|
|
|
{ |
128
|
|
|
return [ |
129
|
|
|
KernelEvents::RESPONSE => 'onKernelResponse', |
130
|
2 |
|
]; |
131
|
|
|
} |
132
|
|
|
|
133
|
2 |
|
/** |
134
|
|
|
* Get the tags from the annotations on the controller that was used in the |
135
|
|
|
* request. |
136
|
|
|
* |
137
|
|
|
* @return array List of tags affected by the request |
138
|
|
|
*/ |
139
|
|
|
private function getAnnotationTags(Request $request) |
140
|
|
|
{ |
141
|
|
|
// Check for _tag request attribute that is set when using @Tag |
142
|
|
|
// annotation |
143
|
|
|
/** @var $tagConfigurations Tag[] */ |
144
|
|
|
if (!$tagConfigurations = $request->attributes->get('_tag')) { |
145
|
24 |
|
return []; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$tags = []; |
149
|
|
|
foreach ($tagConfigurations as $tagConfiguration) { |
150
|
24 |
|
if (null !== $tagConfiguration->getExpression()) { |
151
|
13 |
|
$tags[] = $this->evaluateTag( |
152
|
|
|
$tagConfiguration->getExpression(), |
153
|
|
|
$request |
154
|
11 |
|
); |
155
|
11 |
|
} else { |
156
|
11 |
|
$tags = array_merge($tags, $tagConfiguration->getTags()); |
157
|
3 |
|
} |
158
|
3 |
|
} |
159
|
|
|
|
160
|
|
|
return $tags; |
161
|
|
|
} |
162
|
9 |
|
|
163
|
|
|
/** |
164
|
|
|
* Evaluate a tag that contains expressions. |
165
|
|
|
* |
166
|
11 |
|
* @param string $expression |
167
|
|
|
* |
168
|
|
|
* @return string Evaluated tag |
|
|
|
|
169
|
|
|
*/ |
170
|
|
|
private function evaluateTag($expression, Request $request) |
171
|
|
|
{ |
172
|
|
|
$values = $request->attributes->all(); |
173
|
|
|
// if there is an attribute called "request", it needs to be accessed through the request. |
174
|
|
|
$values['request'] = $request; |
175
|
|
|
|
176
|
|
|
return $this->getExpressionLanguage()->evaluate($expression, $values); |
177
|
8 |
|
} |
178
|
|
|
|
179
|
8 |
View Code Duplication |
private function getExpressionLanguage(): ExpressionLanguage |
|
|
|
|
180
|
|
|
{ |
181
|
8 |
|
if (!$this->expressionLanguage) { |
182
|
|
|
// the expression comes from controller annotations, we can't detect whether they use expressions while building the configuration |
183
|
8 |
|
if (!class_exists(ExpressionLanguage::class)) { |
184
|
|
|
throw new \RuntimeException('Using the tag annotation requires the '.ExpressionLanguage::class.' to be available.'); |
185
|
|
|
} |
186
|
|
|
$this->expressionLanguage = new ExpressionLanguage(); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return $this->expressionLanguage; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.