Skip to content

Commit 7840a39

Browse files
committed
Add abstracted tag handling
Aims to allow support for tagging also on Symfomy HTTP Cache and potentially other caches by limiting the feature to focus on only tags and not any random header. Closes #234
1 parent ae8fd48 commit 7840a39

File tree

12 files changed

+328
-247
lines changed

12 files changed

+328
-247
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ This library integrates your PHP applications with HTTP caching proxies such as
1212
Use this library to send invalidation requests from your application to the caching proxy
1313
and to test your caching and invalidation code against a Varnish setup.
1414

15+
It does this by abstracting some caching concepts and attempting to make sure these
16+
can be supported across Varnish, Nginx and Symfony HttpCache.
17+
1518
If you use Symfony2, have a look at the
1619
[FOSHttpCacheBundle](https://github.com/FriendsOfSymfony/FOSHttpCacheBundle).
1720
The bundle provides the invalidator as a service, along with a number of
@@ -22,6 +25,7 @@ Features
2225

2326
* Send [cache invalidation requests](http://foshttpcache.readthedocs.org/en/stable/cache-invalidator.html)
2427
with minimal impact on performance.
28+
* Cache tagging abstraction, uses BAN with Varnish and allows tagging support for other caching proxies in the future.
2529
* Use the built-in support for [Varnish](http://foshttpcache.readthedocs.org/en/stable/varnish-configuration.html)
2630
3 and 4, [NGINX](http://foshttpcache.readthedocs.org/en/stable/nginx-configuration.html), the
2731
[Symfony reverse proxy from the http-kernel component](http://foshttpcache.readthedocs.org/en/stable/symfony-cache-configuration.html)

src/CacheInvalidator.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
use FOS\HttpCache\Exception\ProxyUnreachableException;
1818
use FOS\HttpCache\Exception\UnsupportedProxyOperationException;
1919
use FOS\HttpCache\ProxyClient\ProxyClientInterface;
20+
use FOS\HttpCache\ProxyClient\Invalidation\TagsInterface;
2021
use FOS\HttpCache\ProxyClient\Invalidation\BanInterface;
2122
use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface;
2223
use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface;
2324
use Symfony\Component\EventDispatcher\EventDispatcher;
2425
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
25-
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2626

2727
/**
2828
* Manages HTTP cache invalidation.
2929
*
3030
* @author David de Boer <[email protected]>
3131
* @author David Buchmann <[email protected]>
32+
* @author André Rømcke <[email protected]>
3233
*/
3334
class CacheInvalidator
3435
{
@@ -47,6 +48,11 @@ class CacheInvalidator
4748
*/
4849
const INVALIDATE = 'invalidate';
4950

51+
/**
52+
* Value to check support of invalidateTags operation.
53+
*/
54+
const TAGS = 'tags';
55+
5056
/**
5157
* @var ProxyClientInterface
5258
*/
@@ -90,6 +96,8 @@ public function supports($operation)
9096
return $this->cache instanceof RefreshInterface;
9197
case self::INVALIDATE:
9298
return $this->cache instanceof BanInterface;
99+
case self::TAGS:
100+
return $this->cache instanceof TagsInterface;
93101
default:
94102
throw new InvalidArgumentException('Unknown operation ' . $operation);
95103
}
@@ -197,6 +205,27 @@ public function invalidate(array $headers)
197205
return $this;
198206
}
199207

208+
/**
209+
* Remove/Expire cache objects based on cache tags
210+
*
211+
* @see TagsInterface::tags()
212+
*
213+
* @param array $tags Tags that should be removed/expired from the cache
214+
*
215+
* @throws UnsupportedProxyOperationException If HTTP cache does not support Tags invalidation
216+
*
217+
* @return $this
218+
*/
219+
public function invalidateTags(array $tags)
220+
{
221+
if (!$this->cache instanceof TagsInterface) {
222+
throw UnsupportedProxyOperationException::cacheDoesNotImplement('Tags');
223+
}
224+
$this->cache->invalidateTags($tags);
225+
226+
return $this;
227+
}
228+
200229
/**
201230
* Invalidate URLs based on a regular expression for the URI, an optional
202231
* content type and optional limit to certain hosts.

src/Handler/TagHandler.php

Lines changed: 0 additions & 142 deletions
This file was deleted.

src/ProxyClient/AbstractProxyClient.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,23 @@ private function handleErrorResponses(array $responses)
150150

151151
return $exceptions;
152152
}
153+
154+
/**
155+
* Make sure that the tags are valid.
156+
*
157+
* Reusable function for proxy clients.
158+
* Escapes `,` and `\n` (newline) characters.
159+
*
160+
* @param array $tags The tags to escape.
161+
*
162+
* @return array Sane tags.
163+
*/
164+
protected function escapeTags(array $tags)
165+
{
166+
array_walk($tags, function (&$tag) {
167+
$tag = str_replace([',', "\n"], ['_', '_'], $tag);
168+
});
169+
170+
return $tags;
171+
}
153172
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\ProxyClient\Invalidation;
13+
14+
use FOS\HttpCache\ProxyClient\ProxyClientInterface;
15+
16+
/**
17+
* An HTTP cache that supports invalidation by a cache tag, that is, removing, or expiring
18+
* objects from the cache tagged with a given tag or set of tags.
19+
*/
20+
interface TagsInterface extends ProxyClientInterface
21+
{
22+
/**
23+
* Remove/Expire cache objects based on cache tags
24+
*
25+
* @param array $tags Tags that should be removed/expired from the cache
26+
*
27+
* @return $this
28+
*/
29+
public function invalidateTags(array $tags);
30+
31+
/**
32+
* Get the value for the HTTP tag header.
33+
*
34+
* This concatenates all tags and ensures correct encoding.
35+
*
36+
* @param array $tags
37+
*
38+
* @return string
39+
*/
40+
public function getTagsHeaderValue(array $tags);
41+
42+
/**
43+
* Get the HTTP header name that will hold cache tags.
44+
*
45+
* @return string
46+
*/
47+
public function getTagsHeaderName();
48+
}

src/ProxyClient/Varnish.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use FOS\HttpCache\ProxyClient\Invalidation\BanInterface;
1717
use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface;
1818
use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface;
19+
use FOS\HttpCache\ProxyClient\Invalidation\TagsInterface;
1920
use FOS\HttpCache\ProxyClient\Request\InvalidationRequest;
2021
use FOS\HttpCache\ProxyClient\Request\RequestQueue;
2122
use Http\Adapter\HttpAdapter;
@@ -25,7 +26,7 @@
2526
*
2627
* @author David de Boer <[email protected]>
2728
*/
28-
class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterface, RefreshInterface
29+
class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterface, RefreshInterface, TagsInterface
2930
{
3031
const HTTP_METHOD_BAN = 'BAN';
3132
const HTTP_METHOD_PURGE = 'PURGE';
@@ -52,16 +53,23 @@ class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterfac
5253
*/
5354
private $baseUriSet;
5455

56+
/**
57+
* @var string
58+
*/
59+
private $tagsHeader;
60+
5561
/**
5662
* {@inheritdoc}
5763
*/
5864
public function __construct(
5965
array $servers,
6066
$baseUri = null,
61-
HttpAdapter $httpAdapter = null
67+
HttpAdapter $httpAdapter = null,
68+
$tagsHeader = 'X-Cache-Tags'
6269
) {
6370
parent::__construct($servers, $baseUri, $httpAdapter);
6471
$this->baseUriSet = $baseUri !== null;
72+
$this->tagsHeader = $tagsHeader;
6573
}
6674

6775
/**
@@ -86,6 +94,34 @@ public function setDefaultBanHeader($name, $value)
8694
$this->defaultBanHeaders[$name] = $value;
8795
}
8896

97+
/**
98+
* {@inheritdoc}
99+
*/
100+
public function invalidateTags(array $tags)
101+
{
102+
$tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $this->escapeTags($tags))));
103+
104+
return $this->ban([$this->tagsHeader => $tagExpression]);
105+
}
106+
107+
/**
108+
* {@inheritdoc}
109+
*/
110+
public function getTagsHeaderValue(array $tags)
111+
{
112+
return implode(',', array_unique($this->escapeTags($tags)));
113+
}
114+
115+
/**
116+
* Get the HTTP header name that will hold cache tags.
117+
*
118+
* @return string
119+
*/
120+
public function getTagsHeaderName()
121+
{
122+
return $this->tagsHeader;
123+
}
124+
89125
/**
90126
* {@inheritdoc}
91127
*/

0 commit comments

Comments
 (0)