Skip to content

Commit 2854cfc

Browse files
dbuddeboer
authored andcommitted
allow to configure tags on routes as constants or with expressions
cleanup configuration moving validation into configuration class
1 parent 5d8092e commit 2854cfc

File tree

8 files changed

+170
-82
lines changed

8 files changed

+170
-82
lines changed

DependencyInjection/Configuration.php

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1010

1111
/**
12-
* This is the class that validates and merges configuration from your app/config files
12+
* This class contains the configuration information for the bundle
1313
*
14-
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
14+
* This information is solely responsible for how the different configuration
15+
* sections are normalized, and merged.
16+
*
17+
* @author David de Boer <[email protected]>
18+
* @author David Buchmann <[email protected]>
1519
*/
1620
class Configuration implements ConfigurationInterface
1721
{
@@ -24,6 +28,18 @@ public function getConfigTreeBuilder()
2428
$rootNode = $treeBuilder->root('fos_http_cache');
2529

2630
$rootNode
31+
->validate()
32+
->ifTrue(function ($v) {return $v['cache_manager']['enabled'] && !isset($v['proxy_client']);})
33+
->then(function ($v) {
34+
if ('auto' === $v['cache_manager']['enabled']) {
35+
$v['cache_manager']['enabled'] = false;
36+
37+
return $v;
38+
}
39+
throw new InvalidConfigurationException('You need to configure a proxy_client to use the cache_manager.');
40+
})
41+
->end()
42+
2743
->children()
2844
->booleanNode('debug')
2945
->defaultValue('%kernel.debug%')
@@ -39,9 +55,8 @@ public function getConfigTreeBuilder()
3955
$this->addUserContextListenerSection($rootNode);
4056
$this->addRulesSection($rootNode);
4157
$this->addProxyClientSection($rootNode);
42-
$this->addTagListenerSection($rootNode);
58+
$this->addCacheManager($rootNode);
4359
$this->addFlashMessageListenerSection($rootNode);
44-
$this->addInvalidatorsSection($rootNode);
4560

4661
return $treeBuilder;
4762
}
@@ -106,13 +121,18 @@ private function addRulesSection(ArrayNodeDefinition $rootNode)
106121

107122
$this->addMatchSection($rules);
108123
$this->addHeaderSection($rules);
124+
$this->addTagSection($rules);
125+
}
126+
127+
private function addTagSection(NodeBuilder $rules)
128+
{
109129
$rules
110130
->arrayNode('tags')
111131
->prototype('scalar')
112-
->validate()
113-
->ifTrue(function ($v) {return !count($v);})
114-
->thenUnset()
115-
->end()
132+
->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
133+
->end()->end()
134+
->arrayNode('tag_expressions')
135+
->prototype('scalar')
116136
->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
117137
->end()
118138
;
@@ -231,9 +251,37 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
231251
->end();
232252
}
233253

234-
private function addTagListenerSection(ArrayNodeDefinition $rootNode)
254+
private function addCacheManager(ArrayNodeDefinition $rootNode)
235255
{
236-
$rootNode
256+
$invalidationNode = $rootNode
257+
->children()
258+
->arrayNode('cache_manager')
259+
->addDefaultsIfNotSet()
260+
->beforeNormalization()
261+
->ifArray()
262+
->then(function ($v) {
263+
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
264+
265+
return $v;
266+
})
267+
->end()
268+
->info('Configure the cache manager. Needs a proxy_client to be configured.')
269+
->children()
270+
->enumNode('enabled')
271+
->values(array(true, false, 'auto'))
272+
->defaultValue('auto')
273+
->info('Allows to disable the invalidation manager. Enabled by default if you configure a proxy client.')
274+
->end()
275+
->end()
276+
;
277+
278+
$this->addTagListenerSection($invalidationNode);
279+
$this->addInvalidatorsSection($invalidationNode);
280+
}
281+
282+
private function addTagListenerSection(ArrayNodeDefinition $invalidationNode)
283+
{
284+
$invalidationNode
237285
->children()
238286
->arrayNode('tag_listener')
239287
->addDefaultsIfNotSet()
@@ -282,21 +330,24 @@ private function addFlashMessageListenerSection(ArrayNodeDefinition $rootNode)
282330
->end();
283331
}
284332

285-
private function addInvalidatorsSection(ArrayNodeDefinition $rootNode)
333+
private function addInvalidatorsSection(ArrayNodeDefinition $invalidationNode)
286334
{
287-
$rootNode
335+
$invalidationNode
288336
->children()
289-
->arrayNode('invalidators')
337+
->arrayNode('route_invalidators')
290338
->useAttributeAsKey('name')
339+
->info('Groups of origin routes that invalidate target routes when a request is made')
291340
->prototype('array')
292341
->children()
293342
->arrayNode('origin_routes')
294343
->isRequired()
295344
->requiresAtLeastOneElement()
296345
->prototype('scalar')->end()
346+
->info('Invalidate the target routes in this group when one of these routes is called')
297347
->end()
298348
->arrayNode('invalidate_routes')
299349
->useAttributeAsKey('name')
350+
->info('Target routes to invalidate when an origin route is called')
300351
->prototype('array')
301352
->children()
302353
->scalarNode('parameter_mapper')->end()

DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,11 @@ public function load(array $configs, ContainerBuilder $container)
3434
}
3535

3636
if (isset($config['proxy_client'])) {
37-
$container->setParameter($this->getAlias().'.invalidators', $config['invalidators']);
3837
$this->loadProxyClient($container, $loader, $config['proxy_client']);
38+
}
3939

40-
$loader->load('cache_manager.xml');
41-
42-
if ($config['tag_listener']['enabled']) {
43-
// true or auto
44-
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
45-
$loader->load('tag_listener.xml');
46-
} elseif (true === $config['tag_listener']['enabled']) {
47-
// silently skip if set to auto
48-
throw new InvalidConfigurationException('The TagListener requires symfony/expression-language');
49-
}
50-
}
51-
52-
if (version_compare(Kernel::VERSION, '2.4.0', '>=')) {
53-
$container
54-
->getDefinition('fos_http_cache.command.invalidate_path')
55-
->addTag('console.command')
56-
;
57-
}
58-
} elseif (!empty($config['invalidators'])) {
59-
throw new InvalidConfigurationException('You need to configure a proxy client to use the invalidators.');
60-
} elseif (true === $config['tag_listener']['enabled']) {
61-
throw new InvalidConfigurationException('You need to configure a proxy client to use the tag listener.');
40+
if ($config['cache_manager']['enabled'] && isset($config['proxy_client'])) {
41+
$this->loadCacheManager($container, $loader, $config['cache_manager']);
6242
}
6343

6444
if (!empty($config['rules'])) {
@@ -82,9 +62,11 @@ public function load(array $configs, ContainerBuilder $container)
8262

8363
/**
8464
* @param ContainerBuilder $container
85-
* @param $config
65+
* @param array $config
66+
*
67+
* @throws InvalidConfigurationException
8668
*/
87-
protected function loadRules(ContainerBuilder $container, $config)
69+
protected function loadRules(ContainerBuilder $container, array $config)
8870
{
8971
foreach ($config['rules'] as $rule) {
9072
$match = $rule['match'];
@@ -112,13 +94,18 @@ protected function loadRules(ContainerBuilder $container, $config)
11294
$extraCriteria
11395
);
11496

115-
if (isset($rule['tags']) && count($rule['tags'])) {
97+
$tags = array(
98+
'tags' => $rule['tags'],
99+
'expressions' => $rule['tag_expressions'],
100+
);
101+
if (count($tags['tags']) || count($tags['expressions'])) {
116102
if (!$container->hasDefinition($this->getAlias() . '.event_listener.tag')) {
117103
throw new InvalidConfigurationException('To configure tags, you need to have the tag event listener enabled, requiring symfony/expression-language');
118104
}
105+
119106
$container
120107
->getDefinition($this->getAlias() . '.event_listener.tag')
121-
->addMethodCall('addRule', array($ruleMatcher, $rule['tags']))
108+
->addMethodCall('addRule', array($ruleMatcher, $tags))
122109
;
123110
}
124111

@@ -221,6 +208,37 @@ protected function loadVarnish(ContainerBuilder $container, XmlFileLoader $loade
221208
$container->setParameter($this->getAlias() . '.proxy_client.varnish.base_url', $config['base_url']);
222209
}
223210

211+
protected function loadCacheManager(ContainerBuilder $container, XmlFileLoader $loader, array $config)
212+
{
213+
$container->setParameter($this->getAlias().'.cache_manager.route_invalidators', $config['route_invalidators']);
214+
215+
$container->setParameter(
216+
$this->getAlias() . '.cache_manager.additional_status',
217+
isset($config['additional_status']) ? $config['additional_status'] : array()
218+
);
219+
$container->setParameter(
220+
$this->getAlias() . '.cache_manager.match_response',
221+
isset($config['match_response']) ? $config['match_response'] : null
222+
);
223+
$loader->load('cache_manager.xml');
224+
if (version_compare(Kernel::VERSION, '2.4.0', '>=')) {
225+
$container
226+
->getDefinition('fos_http_cache.command.invalidate_path')
227+
->addTag('console.command')
228+
;
229+
}
230+
231+
if ($config['tag_listener']['enabled']) {
232+
// true or auto
233+
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
234+
$loader->load('tag_listener.xml');
235+
} elseif (true === $config['tag_listener']['enabled']) {
236+
// silently skip if set to auto
237+
throw new InvalidConfigurationException('The TagListener requires symfony/expression-language');
238+
}
239+
}
240+
}
241+
224242
private function validateUrl($url, $msg)
225243
{
226244
if (false === strpos($url, '://')) {

EventListener/TagSubscriber.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,30 @@ public function onKernelResponse(FilterResponseEvent $event)
5858
if ($response->isSuccessful()) {
5959
$tags = $this->getAnnotationTags($request);
6060
}
61-
$configuredTags = $this->matchConfiguration($request, $response) ?: array();
62-
/*
63-
foreach ($configuredTags as $id => $tag) {
64-
$configuredTags[$id] = $this->expressionLanguage->evaluate($tag, array(
65-
'request' => $request,
66-
'response' => $response,
67-
));
68-
}
69-
*/
7061

71-
$uniqueTags = array_values(array_unique(array_merge($tags, $configuredTags)));
62+
$configuredTags = $this->matchConfiguration($request, $response);
63+
if ($configuredTags) {
64+
foreach ($configuredTags['tags'] as $tag) {
65+
$tags[] = $tag;
66+
}
67+
foreach ($configuredTags['expressions'] as $expression) {
68+
$tags[] = $this->expressionLanguage->evaluate($expression, array(
69+
'request' => $request,
70+
'response' => $response,
71+
));
72+
}
73+
}
7274

73-
if (!count($uniqueTags)) {
75+
if (!count($tags)) {
7476
return;
7577
}
7678

7779
if ($request->isMethodSafe()) {
7880
// For safe requests (GET and HEAD), set cache tags on response
79-
$this->cacheManager->tagResponse($response, $uniqueTags);
81+
$this->cacheManager->tagResponse($response, $tags);
8082
} else {
8183
// For non-safe methods, invalidate the tags
82-
$this->cacheManager->invalidateTags($uniqueTags);
84+
$this->cacheManager->invalidateTags($tags);
8385
}
8486
}
8587

Resources/config/cache_manager.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
class="FOS\HttpCacheBundle\Invalidator\InvalidatorCollection"
1717
factory-class="FOS\HttpCacheBundle\Invalidator\InvalidatorCollectionFactory"
1818
factory-method="getInvalidatorCollection">
19-
<argument>%fos_http_cache.invalidators%</argument>
19+
<argument>%fos_http_cache.cache_manager.route_invalidators%</argument>
2020
</service>
2121

2222
<service id="fos_http_cache.cache_manager"

Resources/doc/invalidation-configuration.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ You can configure invalidators as follows:
2222
# app/config/config.yml
2323

2424
fos_http_cache:
25-
...
26-
invalidators:
27-
villains:
28-
origin_routes: [ villain_edit, villain_delete, villain_publish ]
29-
invalidate_routes:
30-
villains_index: ~ # e.g., /villains
31-
villain_details: ~ # e.g., /villain/{id}
32-
another_invalidator:
33-
origin_routes: [ ... ]
34-
invalidate_routes:
35-
...
25+
...
26+
cache_manager:
27+
route_invalidation:
28+
villains:
29+
origin_routes: [ villain_edit, villain_delete, villain_publish ]
30+
invalidate_routes:
31+
villains_index: ~ # e.g., /villains
32+
villain_details: ~ # e.g., /villain/{id}
33+
another_invalidator:
34+
origin_routes: [ ... ]
35+
invalidate_routes:
36+
...
3637
```
3738

3839
Now when a request to either one of the three origin routes returns a 200 response, both `villains_index` and

Resources/doc/tagging.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ You need to configure your caching proxy to support cache tagging. See the FOSHt
3232
documentation’s [Varnish Configuration chapter](https://github.com/FriendsOfSymfony/FOSHttpCache/blob/master/doc/varnish-configuration.md#tagging)
3333
for more details.
3434

35-
The tag system is controlled by `fos_http_cache.tag_listener.enabled`. By
36-
default, this setting is on `auto`, meaning tagging is activated if you have a
35+
The tag system is controlled by `fos_http_cache.cache_manager.tag_listener.enabled`.
36+
By default, this setting is on `auto`, meaning tagging is activated if you have a
3737
proxy client configured and you have `symfony/expression-language` available in
3838
your project. If you use tagging, it is recommended to set enabled to true to
3939
be notified if your setup is broken:
4040

4141
```yaml
4242
fos_http_cache:
43-
tag_listener:
44-
enabled: true
43+
cache_manager:
44+
tag_listener:
45+
enabled: true
4546
```
4647
4748
Tagging with the Cache Manager

Tests/Unit/DependencyInjection/FOSHttpCacheExtensionTest.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,26 @@ public function testEmptyConfig()
4040
public function testConfigLoadInvalidators()
4141
{
4242
$config = $this->getBaseConfig() + array(
43-
'invalidators' => array(
44-
array(
45-
'name' => 'invalidator1',
46-
'origin_routes' => array(
47-
'my_route'
48-
),
49-
'invalidate_routes' => array(
50-
array(
51-
'name' => 'invalidate_route1',
43+
'cache_manager' => array(
44+
'route_invalidators' => array(
45+
array(
46+
'name' => 'invalidator1',
47+
'origin_routes' => array(
48+
'my_route'
49+
),
50+
'invalidate_routes' => array(
51+
array(
52+
'name' => 'invalidate_route1',
53+
)
5254
)
5355
)
5456
)
55-
)
57+
),
58+
'proxy_client' => array(
59+
'varnish' => array(
60+
'servers' => array('1.2.3.4'),
61+
),
62+
),
5663
);
5764

5865
$container = new ContainerBuilder();

0 commit comments

Comments
 (0)