Skip to content

Commit 11ea3ea

Browse files
committed
Merge pull request #121 from FriendsOfSymfony/cleanup-invalidation-annotation
Cleanup invalidation annotation
2 parents c166470 + 3697d9f commit 11ea3ea

18 files changed

+273
-91
lines changed

Configuration/InvalidateRoute.php

+22
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace FOS\HttpCacheBundle\Configuration;
1313

1414
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation;
15+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1516

1617
/**
1718
* @Annotation
@@ -54,6 +55,27 @@ public function getName()
5455
*/
5556
public function setParams($params)
5657
{
58+
if (!is_array($params)) {
59+
throw new \RuntimeException('InvalidateRoute params must be an array');
60+
}
61+
foreach ($params as $name => $value) {
62+
if (is_array($value)) {
63+
if (1 !== count($value) || !isset($value['expression'])) {
64+
throw new \RuntimeException(sprintf(
65+
'@InvalidateRoute param %s must be string or {"expression"="<expression>"}',
66+
$name,
67+
print_r($value, true)
68+
));
69+
}
70+
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
71+
throw new InvalidConfigurationException(sprintf(
72+
'@InvalidateRoute param %s uses an expression but the ExpressionLanguage is not available.',
73+
$name
74+
));
75+
}
76+
}
77+
}
78+
5779
$this->params = $params;
5880
}
5981

Configuration/Tag.php

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCacheBundle\Exception\InvalidTagException;
1515
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation;
16+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1617

1718
/**
1819
* @Annotation
@@ -32,6 +33,9 @@ public function setValue($data)
3233
*/
3334
public function setExpression($expression)
3435
{
36+
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
37+
throw new InvalidConfigurationException('@Tag param %s uses an expression but the ExpressionLanguage is not available.');
38+
}
3539
$this->expression = $expression;
3640
}
3741

DependencyInjection/Configuration.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ private function addMatch(NodeBuilder $rules)
181181
->ifTrue(function ($v) {return !empty($v['additional_cacheable_status']) && !empty($v['match_response']);})
182182
->thenInvalid('You may not set both additional_cacheable_status and match_response.')
183183
->end()
184+
->validate()
185+
->ifTrue(function ($v) {return !empty($v['match_response']) && !class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage');})
186+
->thenInvalid('Configured a match_response but ExpressionLanugage is not available')
187+
->end()
184188
->children()
185189
->scalarNode('path')
186190
->defaultNull()
@@ -324,7 +328,7 @@ private function addTagSection(ArrayNodeDefinition $rootNode)
324328
->enumNode('enabled')
325329
->values(array(true, false, 'auto'))
326330
->defaultValue('auto')
327-
->info('Allows to disable the listener for tag annotations when your project does not use the annotations. Enabled by default if you have expression language and the cache manager.')
331+
->info('Allows to disable the event subscriber for tag configuration and annotations when your project does not use the annotations. Enabled by default if you configured the cache manager.')
328332
->end()
329333
->scalarNode('header')
330334
->defaultValue('X-Cache-Tags')
@@ -334,6 +338,10 @@ private function addTagSection(ArrayNodeDefinition $rootNode)
334338
->prototype('array')
335339
->fixXmlConfig('tag')
336340
->fixXmlConfig('tag_expression')
341+
->validate()
342+
->ifTrue(function ($v) {return !empty($v['tag_expressions']) && !class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage');})
343+
->thenInvalid('Configured a tag_expression but ExpressionLanugage is not available')
344+
->end()
337345
->children();
338346

339347
$this->addMatch($rules);
@@ -361,7 +369,7 @@ private function addInvalidationSection(ArrayNodeDefinition $rootNode)
361369
->enumNode('enabled')
362370
->values(array(true, false, 'auto'))
363371
->defaultValue('auto')
364-
->info('Allows to disable the listener for invalidation annotations when your project does not use the annotations. Enabled by default if you have expression language and the cache manager.')
372+
->info('Allows to disable the listener for invalidation. Enabled by default if the cache manager is configured. When disabled, the cache manager is no longer flushed automatically.')
365373
->end()
366374
->arrayNode('rules')
367375
->info('Set what requests should invalidate which target routes.')

DependencyInjection/FOSHttpCacheExtension.php

+6-17
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,9 @@ public function load(array $configs, ContainerBuilder $container)
6464

6565
if ($config['tags']['enabled']) {
6666
// true or auto
67-
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
68-
$loader->load('tag_listener.xml');
69-
if (!empty($config['tags']['rules'])) {
70-
$this->loadTagRules($container, $config['tags']['rules']);
71-
}
72-
} elseif (true === $config['tags']['enabled']) {
73-
// silently skip if set to auto
74-
throw new InvalidConfigurationException('The TagSubscriber requires symfony/expression-language and needs the cache_manager to be configured');
67+
$loader->load('tag_listener.xml');
68+
if (!empty($config['tags']['rules'])) {
69+
$this->loadTagRules($container, $config['tags']['rules']);
7570
}
7671

7772
$tagsHeader = $config['tags']['header'];
@@ -81,15 +76,9 @@ public function load(array $configs, ContainerBuilder $container)
8176
}
8277

8378
if ($config['invalidation']['enabled']) {
84-
// true or auto
85-
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
86-
$loader->load('invalidation_listener.xml');
87-
if (!empty($config['invalidation']['rules'])) {
88-
$this->loadInvalidatorRules($container, $config['invalidation']['rules']);
89-
}
90-
} elseif (true === $config['invalidation']['enabled']) {
91-
// silently skip if set to auto
92-
throw new InvalidConfigurationException('The InvalidationSubscriber requires symfony/expression-language and needs the cache_manager to be configured');
79+
$loader->load('invalidation_listener.xml');
80+
if (!empty($config['invalidation']['rules'])) {
81+
$this->loadInvalidatorRules($container, $config['invalidation']['rules']);
9382
}
9483
}
9584

EventListener/InvalidationSubscriber.php

+2-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use FOS\HttpCacheBundle\CacheManager;
1616
use FOS\HttpCacheBundle\Configuration\InvalidatePath;
1717
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
18-
use FOS\HttpCacheBundle\Invalidator\InvalidatorCollection;
1918
use Symfony\Component\Console\ConsoleEvents;
2019
use Symfony\Component\Console\Event\ConsoleEvent;
2120
use Symfony\Component\Console\Output\OutputInterface;
@@ -210,11 +209,8 @@ private function invalidateRoutes(array $routes, Request $request)
210209
if (null !== $route->getParams()) {
211210
// Iterate over route params and try to evaluate their values
212211
foreach ($route->getParams() as $key => $value) {
213-
try {
214-
$value = $this->getExpressionLanguage()->evaluate($value, $request->attributes->all());
215-
} catch (SyntaxError $e) {
216-
// If a syntax error occurred, we assume the param was
217-
// no expression
212+
if (is_array($value)) {
213+
$value = $this->getExpressionLanguage()->evaluate($value['expression'], $request->attributes->all());
218214
}
219215

220216
$params[$key] = $value;

EventListener/TagSubscriber.php

+16-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function __construct(
4747
ExpressionLanguage $expressionLanguage = null
4848
) {
4949
$this->cacheManager = $cacheManager;
50-
$this->expressionLanguage = $expressionLanguage ?: new ExpressionLanguage();
50+
$this->expressionLanguage = $expressionLanguage;
5151
}
5252

5353
/**
@@ -146,10 +146,24 @@ private function getAnnotationTags(Request $request)
146146
*/
147147
private function evaluateTag($expression, Request $request)
148148
{
149-
return $this->expressionLanguage->evaluate(
149+
return $this->getExpressionLanguage()->evaluate(
150150
$expression,
151151
$request->attributes->all()
152152
);
153153
}
154154

155+
/**
156+
* Delay instantiating the expression language instance until we need it,
157+
* to support a setup with only symfony 2.3.
158+
*
159+
* @return ExpressionLanguage
160+
*/
161+
private function getExpressionLanguage()
162+
{
163+
if (!$this->expressionLanguage) {
164+
$this->expressionLanguage = new ExpressionLanguage();
165+
}
166+
167+
return $this->expressionLanguage;
168+
}
155169
}

Exception/InvalidTagException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ class InvalidTagException extends \InvalidArgumentException
1515
{
1616
public function __construct($tag, $char)
1717
{
18-
parent::__construct(sprintf('Tag %s is invalid because it contains %s'));
18+
parent::__construct(sprintf('Tag %s is invalid because it contains %s', $tag, $char));
1919
}
2020
}

Resources/doc/features/invalidation.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ invalidation from your controllers::
7676
use FOS\HttpCacheBundle\Configuration\InvalidatePath;
7777

7878
/**
79-
* @InvalidatePath("/posts")
80-
* @InvalidatePath("/posts/latest")
79+
* @InvalidatePath("/articles")
80+
* @InvalidatePath("/articles/latest")
8181
* @InvalidateRoute("overview", params={"type" = "latest"})")
82+
* @InvalidateRoute("detail", params={"id" = {"expression"="id"}})")
8283
*/
83-
public function editAction()
84+
public function editAction($id)
8485
{
8586
}
8687

Resources/doc/includes/enabled.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ enabled
33

44
**type**: ``enum``, **default**: ``auto``, **options**: ``true``, ``false``, ``auto``
55

6-
Enabled by default if :ref:`ExpressionLanguage is installed <expression language requirement>`
7-
and you have :doc:`configured a proxy client </reference/configuration/proxy-client>`.
6+
Enabled by default if you have configured the cache manager with
7+
:doc:`a proxy client </reference/configuration/proxy-client>`.

Resources/doc/reference/annotations.rst

+15-13
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ actions are executed.
66

77
.. note::
88

9-
Annotations need the SensioFrameworkExtraBundle. Some features also need
10-
the ExpressionLanguage. Make sure to
9+
Annotations need the SensioFrameworkExtraBundle including registering the
10+
Doctrine AnnotationsRegistry. Some features also need the
11+
ExpressionLanguage. Make sure to
1112
:ref:`installed the dependencies first <requirements>`.
1213

1314
.. _invalidatepath:
@@ -20,8 +21,8 @@ Invalidate a path::
2021
use FOS\HttpCacheBundle\Configuration\InvalidatePath;
2122

2223
/**
23-
* @InvalidatePath("/posts")
24-
* @InvalidatePath("/posts/latest")
24+
* @InvalidatePath("/articles")
25+
* @InvalidatePath("/articles/latest")
2526
*/
2627
public function editAction()
2728
{
@@ -39,25 +40,25 @@ Invalidate a route with parameters::
3940
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
4041

4142
/**
42-
* @InvalidateRoute("posts")
43-
* @InvalidateRoute("posts", params={"type" = "latest"})
43+
* @InvalidateRoute("articles")
44+
* @InvalidateRoute("articles", params={"type" = "latest"})
4445
*/
4546
public function editAction()
4647
{
4748
}
4849

49-
You can also use expressions_ in the route parameter values::
50+
You can also use expressions_ in the route parameter values. This obviously
51+
:ref:`requires the ExpressionLanguage component <requirements>`. To invalidate
52+
route ``articles`` with the ``number`` parameter set to ``123``, do::
5053

5154
/**
52-
* @InvalidateRoute("posts", params={"number" = "id"})
55+
* @InvalidateRoute("articles", params={"number" = {"expression"="id"}})
5356
*/
54-
public function editAction(Request $request)
57+
public function editAction(Request $request, $id)
5558
{
5659
// Assume $request->attributes->get('id') returns 123
5760
}
5861

59-
Route ``posts`` will now be invalidated with value ``123`` for param ``number``.
60-
6162
See :doc:`/features/invalidation` for more information.
6263

6364
.. _tag:
@@ -106,8 +107,9 @@ If you prefer, you can combine tags in one annotation::
106107
* @Tag({"news", "news-list"})
107108
*/
108109

109-
You can also use expressions_ in tags. This will set tag ``news-123`` on the
110-
response::
110+
You can also use expressions_ in tags. This obviously
111+
:ref:`requires the ExpressionLanguage component <requirements>`. The following
112+
example sets the tag ``news-123`` on the Response::
111113

112114
/**
113115
* @Tag(expression="'news-'~id")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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\Tests\Functional\EventListener;
13+
14+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
15+
16+
class InvalidationSubscriberTest extends WebTestCase
17+
{
18+
public function testInvalidateRoute()
19+
{
20+
$client = static::createClient();
21+
22+
$client->getContainer()->mock(
23+
'fos_http_cache.cache_manager',
24+
'\FOS\HttpCacheBundle\CacheManager'
25+
)
26+
->shouldReceive('invalidateRoute')->once()->with('test_noncached', array())
27+
->shouldReceive('invalidateRoute')->once()->with('test_cached', array('id' => 'myhardcodedid'))
28+
->shouldReceive('invalidateRoute')->once()->with('tag_one', array('id' => '42'))
29+
->shouldReceive('flush')->once()
30+
;
31+
32+
$client->request('POST', '/invalidate/route/42');
33+
}
34+
35+
public function testInvalidatePath()
36+
{
37+
$client = static::createClient();
38+
39+
$client->getContainer()->mock(
40+
'fos_http_cache.cache_manager',
41+
'\FOS\HttpCacheBundle\CacheManager'
42+
)
43+
->shouldReceive('invalidatePath')->once()->with('/cached')
44+
->shouldReceive('flush')->once()
45+
;
46+
47+
$client->request('POST', '/invalidate/path');
48+
}
49+
50+
public function testErrorIsNotInvalidated()
51+
{
52+
$client = static::createClient();
53+
54+
$client->getContainer()->mock(
55+
'fos_http_cache.cache_manager',
56+
'\FOS\HttpCacheBundle\CacheManager'
57+
)
58+
->shouldReceive('invalidatePath')->never()
59+
->shouldReceive('flush')->once()
60+
;
61+
62+
$client->request('POST', '/invalidate/error');
63+
}
64+
65+
protected function tearDown()
66+
{
67+
static::createClient()->getContainer()->unmock('fos_http_cache.cache_manager');
68+
}
69+
}

Tests/Functional/EventListener/TagSubscriberTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ public function testAnnotationTagsAreSet()
1919
{
2020
$client = static::createClient();
2121

22-
$client->request('GET', '/test/list');
22+
$client->request('GET', '/tag/list');
2323
$response = $client->getResponse();
2424
$this->assertEquals('all-items,item-123', $response->headers->get('X-Cache-Tags'));
2525

26-
$client->request('GET', '/test/123');
26+
$client->request('GET', '/tag/123');
2727
$response = $client->getResponse();
2828
$this->assertEquals('item-123', $response->headers->get('X-Cache-Tags'));
2929
}
@@ -41,7 +41,7 @@ public function testAnnotationTagsAreInvalidated()
4141
->shouldReceive('flush')->once()
4242
;
4343

44-
$client->request('POST', '/test/123');
44+
$client->request('POST', '/tag/123');
4545
}
4646

4747
public function testErrorIsNotInvalidated()
@@ -56,7 +56,7 @@ public function testErrorIsNotInvalidated()
5656
->shouldReceive('flush')->once()
5757
;
5858

59-
$client->request('POST', '/test/error');
59+
$client->request('POST', '/tag/error');
6060
}
6161

6262
public function testConfigurationTagsAreSet()

0 commit comments

Comments
 (0)