Skip to content

Commit e61d360

Browse files
committed
Merge pull request #162 from FriendsOfSymfony/tag-handler
Separating the tag handler into its own class
2 parents 38add28 + 077aa18 commit e61d360

File tree

7 files changed

+235
-56
lines changed

7 files changed

+235
-56
lines changed

doc/cache-invalidator.rst

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ See below for the :ref:`flush() <flush>` method.
4343
Invalidate a URL::
4444

4545
$cacheInvalidator->invalidatePath('http://www.example.com/users')->flush();
46-
46+
4747
Invalidate a URL with added header(s)::
4848

4949
$cacheInvalidator->invalidatePath(
@@ -124,52 +124,6 @@ To invalidate on a custom header ``X-My-Header``, you would do::
124124

125125
$cacheInvalidator->invalidate(array('X-My-Header' => 'my-value'))->flush();
126126

127-
.. _tags:
128-
129-
Invalidating Tags
130-
-----------------
131-
132-
.. note::
133-
134-
Make sure to :doc:`configure your proxy <proxy-configuration>` for tagging first.
135-
136-
With tags you can group related representations so it becomes easier to
137-
invalidate them. You will have to make sure your web application adds the
138-
correct tags on all responses by setting the ``X-Cache-Tags`` header. The
139-
FOSHttpCacheBundle_ does this for you when you’re using Symfony.
140-
141-
Assume you sent four responses:
142-
143-
+------------+-------------------------+
144-
| Response: | ``X-Cache-Tags`` header:|
145-
+============+=========================+
146-
| ``/one`` | ``tag-one`` |
147-
+------------+-------------------------+
148-
| ``/two`` | ``tag-two, group-a`` |
149-
+------------+-------------------------+
150-
| ``/three`` | ``tag-three, group-a`` |
151-
+------------+-------------------------+
152-
| ``/four`` | ``tag-four, group-b`` |
153-
+------------+-------------------------+
154-
155-
You can now invalidate some URLs using tags::
156-
157-
$cacheInvalidator->invalidateTags(array('group-a', 'tag-four'))->flush();
158-
159-
160-
This will ban all requests having either the tag ``group-a`` /or/ ``tag-four``.
161-
In the above example, this will invalidate ``/two``, ``/three`` and ``/four``.
162-
Only ``/one`` will stay in the cache.
163-
164-
.. _custom_tags_header:
165-
166-
Custom Tags Header
167-
~~~~~~~~~~~~~~~~~~
168-
169-
Tagging uses a custom HTTP header to identify tags. You can change the default
170-
header ``X-Cache-Tags`` by calling ``setTagsHeader()``. Make sure to reflect this
171-
change in your :doc:`caching proxy configuration <proxy-configuration>`.
172-
173127
.. _flush:
174128

175129
Flushing

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Contents:
2323
proxy-configuration
2424
proxy-clients
2525
cache-invalidator
26+
invalidation-handlers
2627
user-context
2728

2829
testing-your-application

doc/invalidation-handlers.rst

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Extra Invalidation Handlers
2+
===========================
3+
4+
This library provides decorators that build on top of the ``CacheInvalidator``
5+
to simplify common operations.
6+
7+
.. _tags:
8+
9+
Tag Handler
10+
-----------
11+
12+
The tag handler helps you to invalidate all cache entries that where marked
13+
with a specified tag. It works only with a ``CacheInvalidator`` that supports
14+
``CacheInvalidator::INVALIDATE``.
15+
16+
Setup
17+
~~~~~
18+
19+
.. note::
20+
21+
Make sure to :doc:`configure your proxy <proxy-configuration>` for tagging first.
22+
23+
The tag handler is a decorator around the ``CacheInvalidator``. After
24+
:doc:`creating the invalidator <cache-invalidator>` with a proxy client
25+
that implements the ``BanInterface``, instantiate the ``TagHandler``::
26+
27+
use FOS\HttpCache\Handler\TagHandler;
28+
29+
// $cacheInvalidator already created as instance of FOS\HttpCache\CacheInvalidator
30+
$tagHandler = new TagHandler($cacheInvalidator);
31+
32+
Usage
33+
~~~~~
34+
35+
With tags you can group related representations so it becomes easier to
36+
invalidate them. You will have to make sure your web application adds the
37+
correct tags on all responses by setting the ``X-Cache-Tags`` header. The
38+
FOSHttpCacheBundle_ does this for you when you’re using Symfony.
39+
40+
Assume you sent four responses:
41+
42+
+------------+-------------------------+
43+
| Response: | ``X-Cache-Tags`` header:|
44+
+============+=========================+
45+
| ``/one`` | ``tag-one`` |
46+
+------------+-------------------------+
47+
| ``/two`` | ``tag-two, group-a`` |
48+
+------------+-------------------------+
49+
| ``/three`` | ``tag-three, group-a`` |
50+
+------------+-------------------------+
51+
| ``/four`` | ``tag-four, group-b`` |
52+
+------------+-------------------------+
53+
54+
You can now invalidate some URLs using tags::
55+
56+
$tagHandler->invalidateTags(array('group-a', 'tag-four'))->flush();
57+
58+
This will ban all requests having either the tag ``group-a`` /or/ ``tag-four``.
59+
In the above example, this will invalidate ``/two``, ``/three`` and ``/four``.
60+
Only ``/one`` will stay in the cache.
61+
62+
.. _custom_tags_header:
63+
64+
Custom Tags Header
65+
~~~~~~~~~~~~~~~~~~
66+
67+
Tagging uses a custom HTTP header to identify tags. You can change the default
68+
header ``X-Cache-Tags`` in the constructor::
69+
70+
use FOS\HttpCache\Handler\TagHandler;
71+
72+
// $cacheInvalidator already created as instance of FOS\HttpCache\CacheInvalidator
73+
$tagHandler = new TagHandler($cacheInvalidator, 'My-Cache-Header');
74+
75+
Make sure to reflect this change in your
76+
:doc:`caching proxy configuration <proxy-configuration>`.

src/CacheInvalidator.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use FOS\HttpCache\ProxyClient\Invalidation\BanInterface;
2121
use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface;
2222
use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface;
23+
use FOS\HttpCache\Handler\TagHandler;
2324
use Symfony\Component\EventDispatcher\EventDispatcher;
2425
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2526
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -28,6 +29,7 @@
2829
* Manages HTTP cache invalidation.
2930
*
3031
* @author David de Boer <[email protected]>
32+
* @author David Buchmann <[email protected]>
3133
*/
3234
class CacheInvalidator
3335
{
@@ -56,6 +58,11 @@ class CacheInvalidator
5658
*/
5759
private $eventDispatcher;
5860

61+
/**
62+
* @var TagHandler
63+
*/
64+
private $tagHandler;
65+
5966
/**
6067
* @var string
6168
*/
@@ -69,6 +76,9 @@ class CacheInvalidator
6976
public function __construct(ProxyClientInterface $cache)
7077
{
7178
$this->cache = $cache;
79+
if ($cache instanceof BanInterface) {
80+
$this->tagHandler = new TagHandler($this, $this->tagsHeader);
81+
}
7282
}
7383

7484
/**
@@ -145,10 +155,14 @@ public function addSubscriber(EventSubscriberInterface $subscriber)
145155
* @param string $tagsHeader
146156
*
147157
* @return $this
158+
*
159+
* @deprecated Use constructor argument to TagHandler instead.
148160
*/
149161
public function setTagsHeader($tagsHeader)
150162
{
151-
$this->tagsHeader = $tagsHeader;
163+
if ($this->tagHandler && $this->tagsHeader !== $tagsHeader) {
164+
$this->tagHandler = new TagHandler($this, $tagsHeader);
165+
}
152166

153167
return $this;
154168
}
@@ -157,6 +171,8 @@ public function setTagsHeader($tagsHeader)
157171
* Get the HTTP header name that will hold cache tags
158172
*
159173
* @return string
174+
*
175+
* @deprecated Use TagHandler::getTagsHeader instead.
160176
*/
161177
public function getTagsHeader()
162178
{
@@ -269,22 +285,21 @@ public function invalidateRegex($path, $contentType = null, $hosts = null)
269285
* Invalidate cache entries that contain any of the specified tags in their
270286
* tag header.
271287
*
272-
* @see BanInterface::ban()
273-
*
274288
* @param array $tags Cache tags
275289
*
276290
* @throws UnsupportedProxyOperationException If HTTP cache does not support BAN requests
277291
*
278292
* @return $this
293+
*
294+
* @deprecated Use TagHandler::invalidateTags instead.
279295
*/
280296
public function invalidateTags(array $tags)
281297
{
282-
if (!$this->cache instanceof BanInterface) {
298+
if (!$this->tagHandler) {
283299
throw UnsupportedProxyOperationException::cacheDoesNotImplement('BAN');
284300
}
285301

286-
$headers = array($this->getTagsHeader() => '('.implode('|', array_map('preg_quote', $tags)).')(,.+)?$');
287-
$this->cache->ban($headers);
302+
$this->tagHandler->invalidateTags($tags);
288303

289304
return $this;
290305
}

src/Handler/TagHandler.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache 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\HttpCache\Handler;
13+
14+
use FOS\HttpCache\CacheInvalidator;
15+
use FOS\HttpCache\Exception\UnsupportedProxyOperationException;
16+
17+
/**
18+
* Handler for cache tagging.
19+
*
20+
* @author David de Boer <[email protected]>
21+
* @author David Buchmann <[email protected]>
22+
*/
23+
class TagHandler
24+
{
25+
/**
26+
* @var CacheInvalidator
27+
*/
28+
private $invalidator;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $tagsHeader;
34+
35+
/**
36+
* Constructor
37+
*
38+
* @param CacheInvalidator $invalidator The invalidator instance.
39+
* @param string $tagsHeader Header to use for tags, defaults to X-Cache-Tags.
40+
*
41+
* @throws UnsupportedProxyOperationException If CacheInvalidator does not support invalidate requests
42+
*/
43+
public function __construct(CacheInvalidator $invalidator, $tagsHeader = 'X-Cache-Tags')
44+
{
45+
if (!$invalidator->supports(CacheInvalidator::INVALIDATE)) {
46+
throw UnsupportedProxyOperationException::cacheDoesNotImplement('BAN');
47+
}
48+
$this->invalidator = $invalidator;
49+
$this->tagsHeader = $tagsHeader;
50+
}
51+
52+
/**
53+
* Invalidate cache entries that contain any of the specified tags in their
54+
* tag header.
55+
*
56+
* @param array $tags Cache tags
57+
*
58+
* @return $this
59+
*/
60+
public function invalidateTags(array $tags)
61+
{
62+
$tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $tags)));
63+
$headers = array($this->tagsHeader => $tagExpression);
64+
$this->invalidator->invalidate($headers);
65+
66+
return $this;
67+
}
68+
}

tests/Unit/Handler/TagHandlerTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache 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\HttpCache\Tests\Unit\Handler;
13+
14+
use FOS\HttpCache\CacheInvalidator;
15+
use FOS\HttpCache\Handler\TagHandler;
16+
17+
class TagHandlerTest extends \PHPUnit_Framework_TestCase
18+
{
19+
public function testInvalidateTags()
20+
{
21+
$cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator')
22+
->shouldReceive('invalidate')
23+
->with(array('X-Cache-Tags' => '(post\-1|posts)(,.+)?$'))
24+
->once()
25+
->shouldReceive('supports')
26+
->with(CacheInvalidator::INVALIDATE)
27+
->once()
28+
->andReturn(true)
29+
->getMock();
30+
31+
$tagHandler = new TagHandler($cacheInvalidator);
32+
$tagHandler->invalidateTags(array('post-1', 'posts'));
33+
}
34+
35+
public function testInvalidateTagsCustomHeader()
36+
{
37+
$cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator')
38+
->shouldReceive('invalidate')
39+
->with(array('Custom-Tags' => '(post\-1)(,.+)?$'))
40+
->once()
41+
->shouldReceive('supports')
42+
->with(CacheInvalidator::INVALIDATE)
43+
->once()
44+
->andReturn(true)
45+
->getMock();
46+
47+
$tagHandler = new TagHandler($cacheInvalidator, 'Custom-Tags');
48+
$tagHandler->invalidateTags(array('post-1'));
49+
}
50+
51+
/**
52+
* @expectedException \FOS\HttpCache\Exception\UnsupportedProxyOperationException
53+
*/
54+
public function testInvalidateUnsupported()
55+
{
56+
$cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator')
57+
->shouldReceive('supports')
58+
->with(CacheInvalidator::INVALIDATE)
59+
->once()
60+
->andReturn(false)
61+
->getMock();
62+
63+
new TagHandler($cacheInvalidator);
64+
}
65+
}

tests/Unit/SymfonyCache/EventDispatchingHttpCacheTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ protected function getHttpCachePartialMock(array $mockedMethods = null)
2828
{
2929
$mock = $this
3030
->getMockBuilder('\FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache')
31-
->setMethods( $mockedMethods )
32-
->disableOriginalConstructor()
33-
->getMock()
31+
->setMethods( $mockedMethods )
32+
->disableOriginalConstructor()
33+
->getMock()
3434
;
3535

3636
// Force setting options property since we can't use original constructor.

0 commit comments

Comments
 (0)