diff --git a/CHANGELOG.md b/CHANGELOG.md index af825888..30febd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC caching proxy. * Refactored the proxy client test system into traits. Removed ProxyTestCase, use the traits `CacheAssertions` and `HttpCaller` instead. +* Abstracting tags by adding new `TagsInterface` for ProxyClients, as part of that also: + BC break: Moved tag invalidation to `CacheInvalidator`, and rename TagHandler to ResponseTagger. 1.4.0 ----- diff --git a/README.md b/README.md index 2f325d2b..823ed928 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ This library integrates your PHP applications with HTTP caching proxies such as Use this library to send invalidation requests from your application to the caching proxy and to test your caching and invalidation code against a Varnish setup. +It does this by abstracting some caching concepts and attempting to make sure these +can be supported across Varnish, Nginx and Symfony HttpCache. + If you use Symfony2, have a look at the [FOSHttpCacheBundle](https://github.com/FriendsOfSymfony/FOSHttpCacheBundle). The bundle provides the invalidator as a service, along with a number of @@ -22,6 +25,7 @@ Features * Send [cache invalidation requests](http://foshttpcache.readthedocs.org/en/stable/cache-invalidator.html) with minimal impact on performance. +* Cache tagging abstraction, uses BAN with Varnish and allows tagging support for other caching proxies in the future. * Use the built-in support for [Varnish](http://foshttpcache.readthedocs.org/en/stable/varnish-configuration.html) 3 and 4, [NGINX](http://foshttpcache.readthedocs.org/en/stable/nginx-configuration.html), the [Symfony reverse proxy from the http-kernel component](http://foshttpcache.readthedocs.org/en/stable/symfony-cache-configuration.html) diff --git a/doc/cache-invalidator.rst b/doc/cache-invalidator.rst index f9fd8b33..f748a25f 100644 --- a/doc/cache-invalidator.rst +++ b/doc/cache-invalidator.rst @@ -82,6 +82,32 @@ Refresh a URL with added header(s):: .. _invalidate regex: + +Invalidating by Tags +-------------------- + +.. note:: + + Make sure to :doc:`configure your proxy ` for tagging first, + in the case of Varnish this is powered by banning. + +When you are using :doc:`response tagging `, you can invalidate +all responses that where tagged with a specific label. + +Invalidate a tag:: + + $cacheInvalidator->invalidateTags(['blog-post-44'])->flush(); + +See below for the :ref:`flush() ` method. + +Invalidate several tags:: + + $cacheInvalidator + ->invalidateTags(['type-65', 'location-3']) + ->flush() + ; + + Invalidating With a Regular Expression -------------------------------------- diff --git a/doc/index.rst b/doc/index.rst index ac200460..0615acd2 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -28,7 +28,7 @@ Contents: proxy-configuration proxy-clients cache-invalidator - invalidation-handlers + response-tagging user-context testing-your-application diff --git a/doc/invalidation-handlers.rst b/doc/invalidation-handlers.rst deleted file mode 100644 index 862867bf..00000000 --- a/doc/invalidation-handlers.rst +++ /dev/null @@ -1,98 +0,0 @@ -Extra Invalidation Handlers -=========================== - -This library provides decorators that build on top of the ``CacheInvalidator`` -to simplify common operations. - -.. _tags: - -Tag Handler ------------ - -The tag handler helps you to mark responses with tags that you can later use to -invalidate all cache entries with that tag. Tag invalidation works only with a -``CacheInvalidator`` that supports ``CacheInvalidator::INVALIDATE``. - -Setup -~~~~~ - -.. note:: - - Make sure to :doc:`configure your proxy ` for tagging first. - -The tag handler is a decorator around the ``CacheInvalidator``. After -:doc:`creating the invalidator ` with a proxy client -that implements the ``BanInterface``, instantiate the ``TagHandler``:: - - use FOS\HttpCache\Handler\TagHandler; - - // $cacheInvalidator already created as instance of FOS\HttpCache\CacheInvalidator - $tagHandler = new TagHandler($cacheInvalidator); - -Usage -~~~~~ - -With tags you can group related representations so it becomes easier to -invalidate them. You will have to make sure your web application adds the -correct tags on all responses. You can add tags to the handler using:: - - $tagHandler->addTags(['tag-two', 'group-a']); - -Before any content is sent out, you need to send the tag header_:: - - header(sprintf('%s: %s'), - $tagHandler->getTagsHeaderName(), - $tagHandler->getTagsHeaderValue() - ); - -.. tip:: - - If you are using Symfony with the FOSHttpCacheBundle_, the tag header is - set automatically. You also have `additional methods of defining tags`_ with - annotations and on URL patterns. - -Assume you sent four responses: - -+------------+-------------------------+ -| Response: | ``X-Cache-Tags`` header:| -+============+=========================+ -| ``/one`` | ``tag-one`` | -+------------+-------------------------+ -| ``/two`` | ``tag-two, group-a`` | -+------------+-------------------------+ -| ``/three`` | ``tag-three, group-a`` | -+------------+-------------------------+ -| ``/four`` | ``tag-four, group-b`` | -+------------+-------------------------+ - -You can now invalidate some URLs using tags:: - - $tagHandler->invalidateTags(['group-a', 'tag-four']); - -This will ban all requests having either the tag ``group-a`` /or/ ``tag-four``. -In the above example, this will invalidate ``/two``, ``/three`` and ``/four``. -Only ``/one`` will stay in the cache. - -.. note:: - - Don't forget to call :ref:`flush ` on the ``CacheInvalidator`` to commit the invalidations - to the caching proxy. - -.. _custom_tags_header: - -Custom Tags Header -~~~~~~~~~~~~~~~~~~ - -Tagging uses a custom HTTP header to identify tags. You can change the default -header ``X-Cache-Tags`` in the constructor:: - - use FOS\HttpCache\Handler\TagHandler; - - // $cacheInvalidator already created as instance of FOS\HttpCache\CacheInvalidator - $tagHandler = new TagHandler($cacheInvalidator, 'My-Cache-Header'); - -Make sure to reflect this change in your -:doc:`caching proxy configuration `. - -.. _header: http://php.net/header -.. _additional methods of defining tags: http://foshttpcachebundle.readthedocs.org/en/latest/features/tagging.html diff --git a/doc/proxy-clients.rst b/doc/proxy-clients.rst index efc52f1a..dcf7231a 100644 --- a/doc/proxy-clients.rst +++ b/doc/proxy-clients.rst @@ -91,6 +91,15 @@ include that port in the base URL:: $varnish = new Varnish($servers, 'my-cool-app.com:8080'); +.. _varnish_custom_tags_header: + +Another optional parameter available on Varnish client is ``tagsHeader``, which allows you to +change the default HTTP header used for tagging, ``X-Cache-Tags``:: + + $varnish = new Varnish($servers, 'example.com', $adapter, 'X-Custom-Tags-Header'); + + Make sure to reflect this change in your :doc:`caching proxy configuration `. + .. note:: To make invalidation work, you need to :doc:`configure Varnish ` accordingly. @@ -267,5 +276,6 @@ Varnish client:: Make sure to add any headers that you want to ban on to your :doc:`proxy configuration `. +.. _header: http://php.net/header .. _HTTP Adapter: http://php-http.readthedocs.org/en/latest/ .. _PSR-7 message implementation: https://packagist.org/providers/psr/http-message-implementation diff --git a/doc/response-tagging.rst b/doc/response-tagging.rst new file mode 100644 index 00000000..5f215b51 --- /dev/null +++ b/doc/response-tagging.rst @@ -0,0 +1,76 @@ +Response Tagging +================ + +The ``ResponseTagger`` helps you keep track tags for a response, which can be +added to response headers that you can later use to invalidate all cache +entries with that tag. + +.. _tags: + +Setup +~~~~~ + +.. note:: + + Make sure to :doc:`configure your proxy ` for tagging first. + +The response tagger is a decorator around a proxy client that implements +the ``TagsInterface``, handling adding tags to responses:: + + use FOS\HttpCache\ResponseTagger; + + // $proxyClient already created, implementing FOS\HttpCache\ProxyClient\Invalidation\TagsInterface + $responseTagger = new ResponseTagger($proxyClient); + +Usage +~~~~~ + +With tags you can group related representations so it becomes easier to +invalidate them. You will have to make sure your web application adds the +correct tags on all responses. You can add tags to the response using:: + + $responseTagger->addTags(['tag-two', 'group-a']); + +Before any content is sent out, you need to send the tag header_:: + + header(sprintf('%s: %s'), + $responseTagger->getTagsHeaderName(), + $responseTagger->getTagsHeaderValue() + ); + +.. tip:: + + If you are using Symfony with the FOSHttpCacheBundle_, the tags + added to ``ResponseTagger`` are added to the response automatically. + You also have `additional methods of defining tags`_ with + annotations and on URL patterns. + +Assume you sent four responses: + ++------------+-------------------------+ +| Response: | ``X-Cache-Tags`` header:| ++============+=========================+ +| ``/one`` | ``tag-one`` | ++------------+-------------------------+ +| ``/two`` | ``tag-two, group-a`` | ++------------+-------------------------+ +| ``/three`` | ``tag-three, group-a`` | ++------------+-------------------------+ +| ``/four`` | ``tag-four, group-b`` | ++------------+-------------------------+ + +You can now invalidate some URLs using tags:: + + $tagHandler->invalidateTags(['group-a', 'tag-four'])->flush(); + +This will ban all requests having either the tag ``group-a`` /or/ ``tag-four``. +In the above example, this will invalidate ``/two``, ``/three`` and ``/four``. +Only ``/one`` will stay in the cache. + +.. note:: + + For further reading on tag invalidation see :doc:`cache-invalidator page `. + For changing the cache header, :doc:`configure your proxy `. + +.. _header: http://php.net/header +.. _additional methods of defining tags: http://foshttpcachebundle.readthedocs.org/en/latest/features/tagging.html diff --git a/doc/varnish-configuration.rst b/doc/varnish-configuration.rst index a158c155..5b0add1e 100644 --- a/doc/varnish-configuration.rst +++ b/doc/varnish-configuration.rst @@ -180,7 +180,7 @@ using an ``X-Cache-Tags`` header. If you need to use a different tag for the headers than the default ``X-Cache-Tags`` used in ``fos_ban.vcl``, you will have to write your own VCL code for tag invalidation and change the tagging header -:ref:`configured in the cache invalidator `. Your custom +:ref:`configured in the cache invalidator `. Your custom VCL will look like this: .. configuration-block:: diff --git a/src/CacheInvalidator.php b/src/CacheInvalidator.php index b657f008..c39af4bf 100644 --- a/src/CacheInvalidator.php +++ b/src/CacheInvalidator.php @@ -17,18 +17,19 @@ use FOS\HttpCache\Exception\ProxyUnreachableException; use FOS\HttpCache\Exception\UnsupportedProxyOperationException; use FOS\HttpCache\ProxyClient\ProxyClientInterface; +use FOS\HttpCache\ProxyClient\Invalidation\TagsInterface; use FOS\HttpCache\ProxyClient\Invalidation\BanInterface; use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface; use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Manages HTTP cache invalidation. * * @author David de Boer * @author David Buchmann + * @author André Rømcke */ class CacheInvalidator { @@ -47,6 +48,11 @@ class CacheInvalidator */ const INVALIDATE = 'invalidate'; + /** + * Value to check support of invalidateTags operation. + */ + const TAGS = 'tags'; + /** * @var ProxyClientInterface */ @@ -90,6 +96,8 @@ public function supports($operation) return $this->cache instanceof RefreshInterface; case self::INVALIDATE: return $this->cache instanceof BanInterface; + case self::TAGS: + return $this->cache instanceof TagsInterface; default: throw new InvalidArgumentException('Unknown operation ' . $operation); } @@ -197,6 +205,27 @@ public function invalidate(array $headers) return $this; } + /** + * Remove/Expire cache objects based on cache tags + * + * @see TagsInterface::tags() + * + * @param array $tags Tags that should be removed/expired from the cache + * + * @throws UnsupportedProxyOperationException If HTTP cache does not support Tags invalidation + * + * @return $this + */ + public function invalidateTags(array $tags) + { + if (!$this->cache instanceof TagsInterface) { + throw UnsupportedProxyOperationException::cacheDoesNotImplement('Tags'); + } + $this->cache->invalidateTags($tags); + + return $this; + } + /** * Invalidate URLs based on a regular expression for the URI, an optional * content type and optional limit to certain hosts. diff --git a/src/Handler/TagHandler.php b/src/Handler/TagHandler.php deleted file mode 100644 index 1bea6eee..00000000 --- a/src/Handler/TagHandler.php +++ /dev/null @@ -1,142 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\HttpCache\Handler; - -use FOS\HttpCache\CacheInvalidator; -use FOS\HttpCache\Exception\UnsupportedProxyOperationException; - -/** - * Handler for cache tagging. - * - * @author David de Boer - * @author David Buchmann - */ -class TagHandler -{ - /** - * @var CacheInvalidator - */ - private $invalidator; - - /** - * @var string - */ - private $tagsHeader; - - /** - * @var array - */ - private $tags = []; - - /** - * Constructor - * - * @param CacheInvalidator $invalidator The invalidator instance. - * @param string $tagsHeader Header to use for tags, defaults to X-Cache-Tags. - * - * @throws UnsupportedProxyOperationException If CacheInvalidator does not support invalidate requests - */ - public function __construct(CacheInvalidator $invalidator, $tagsHeader = 'X-Cache-Tags') - { - if (!$invalidator->supports(CacheInvalidator::INVALIDATE)) { - throw UnsupportedProxyOperationException::cacheDoesNotImplement('BAN'); - } - $this->invalidator = $invalidator; - $this->tagsHeader = $tagsHeader; - } - - /** - * Get the HTTP header name that will hold cache tags. - * - * @return string - */ - public function getTagsHeaderName() - { - return $this->tagsHeader; - } - - /** - * Get the value for the HTTP tag header. - * - * This concatenates all tags and ensures correct encoding. - * - * @return string - */ - public function getTagsHeaderValue() - { - return implode(',', array_unique($this->escapeTags($this->tags))); - } - - /** - * Check whether the tag handler has any tags to set on the response. - * - * @return bool True if this handler will set at least one tag. - */ - public function hasTags() - { - return 0 < count($this->tags); - } - - /** - * Add tags to be set on the response. - * - * This must be called before any HTTP response is sent to the client. - * - * @param array $tags List of tags to add. - * - * @return $this - */ - public function addTags(array $tags) - { - $this->tags = array_merge($this->tags, $tags); - - return $this; - } - - /** - * Invalidate cache entries that contain any of the specified tags in their - * tag header. - * - * The cache manager is told to invalidate all content with the specified - * tags. The invalidaton requests are sent to the caching proxy on kernel - * termination (both for web requests and command runs). To immediately - * send the invalidation request, call the CacheManager::flush() method. - * - * @param array $tags Cache tags - * - * @return $this - */ - public function invalidateTags(array $tags) - { - $tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $this->escapeTags($tags)))); - $headers = [$this->tagsHeader => $tagExpression]; - $this->invalidator->invalidate($headers); - - return $this; - } - - /** - * Make sure that the tags are valid. - * - * @param array $tags The tags to escape. - * - * @return array Sane tags. - */ - protected function escapeTags(array $tags) - { - array_walk($tags, function (&$tag) { - $tag = str_replace([',', "\n"], ['_', '_'], $tag); - }); - - return $tags; - } -} diff --git a/src/ProxyClient/AbstractProxyClient.php b/src/ProxyClient/AbstractProxyClient.php index 0b4b788d..957dc9c2 100644 --- a/src/ProxyClient/AbstractProxyClient.php +++ b/src/ProxyClient/AbstractProxyClient.php @@ -150,4 +150,23 @@ private function handleErrorResponses(array $responses) return $exceptions; } + + /** + * Make sure that the tags are valid. + * + * Reusable function for proxy clients. + * Escapes `,` and `\n` (newline) characters. + * + * @param array $tags The tags to escape. + * + * @return array Sane tags. + */ + protected function escapeTags(array $tags) + { + array_walk($tags, function (&$tag) { + $tag = str_replace([',', "\n"], ['_', '_'], $tag); + }); + + return $tags; + } } diff --git a/src/ProxyClient/Invalidation/TagsInterface.php b/src/ProxyClient/Invalidation/TagsInterface.php new file mode 100644 index 00000000..d3fae289 --- /dev/null +++ b/src/ProxyClient/Invalidation/TagsInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\HttpCache\ProxyClient\Invalidation; + +use FOS\HttpCache\ProxyClient\ProxyClientInterface; + +/** + * An HTTP cache that supports invalidation by a cache tag, that is, removing, or expiring + * objects from the cache tagged with a given tag or set of tags. + */ +interface TagsInterface extends ProxyClientInterface +{ + /** + * Remove/Expire cache objects based on cache tags + * + * @param array $tags Tags that should be removed/expired from the cache + * + * @return $this + */ + public function invalidateTags(array $tags); + + /** + * Get the value for the HTTP tag header. + * + * This concatenates all tags and ensures correct encoding. + * + * @param array $tags + * + * @return string + */ + public function getTagsHeaderValue(array $tags); + + /** + * Get the HTTP header name that will hold cache tags. + * + * @return string + */ + public function getTagsHeaderName(); +} diff --git a/src/ProxyClient/Varnish.php b/src/ProxyClient/Varnish.php index 327adcc6..8e68cc44 100644 --- a/src/ProxyClient/Varnish.php +++ b/src/ProxyClient/Varnish.php @@ -16,6 +16,7 @@ use FOS\HttpCache\ProxyClient\Invalidation\BanInterface; use FOS\HttpCache\ProxyClient\Invalidation\PurgeInterface; use FOS\HttpCache\ProxyClient\Invalidation\RefreshInterface; +use FOS\HttpCache\ProxyClient\Invalidation\TagsInterface; use FOS\HttpCache\ProxyClient\Request\InvalidationRequest; use FOS\HttpCache\ProxyClient\Request\RequestQueue; use Http\Adapter\HttpAdapter; @@ -25,7 +26,7 @@ * * @author David de Boer */ -class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterface, RefreshInterface +class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterface, RefreshInterface, TagsInterface { const HTTP_METHOD_BAN = 'BAN'; const HTTP_METHOD_PURGE = 'PURGE'; @@ -52,16 +53,23 @@ class Varnish extends AbstractProxyClient implements BanInterface, PurgeInterfac */ private $baseUriSet; + /** + * @var string + */ + private $tagsHeader; + /** * {@inheritdoc} */ public function __construct( array $servers, $baseUri = null, - HttpAdapter $httpAdapter = null + HttpAdapter $httpAdapter = null, + $tagsHeader = 'X-Cache-Tags' ) { parent::__construct($servers, $baseUri, $httpAdapter); $this->baseUriSet = $baseUri !== null; + $this->tagsHeader = $tagsHeader; } /** @@ -86,6 +94,34 @@ public function setDefaultBanHeader($name, $value) $this->defaultBanHeaders[$name] = $value; } + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $this->escapeTags($tags)))); + + return $this->ban([$this->tagsHeader => $tagExpression]); + } + + /** + * {@inheritdoc} + */ + public function getTagsHeaderValue(array $tags) + { + return implode(',', array_unique($this->escapeTags($tags))); + } + + /** + * Get the HTTP header name that will hold cache tags. + * + * @return string + */ + public function getTagsHeaderName() + { + return $this->tagsHeader; + } + /** * {@inheritdoc} */ diff --git a/src/ResponseTagger.php b/src/ResponseTagger.php new file mode 100644 index 00000000..9f5e43bc --- /dev/null +++ b/src/ResponseTagger.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\HttpCache; + +use FOS\HttpCache\ProxyClient\Invalidation\TagsInterface; + +/** + * Service for Response cache tagging. + * + * @author David de Boer + * @author David Buchmann + * @author André Rømcke + */ +class ResponseTagger +{ + /** + * @var TagsInterface + */ + private $client; + + /** + * @var array + */ + private $tags = []; + + /** + * Constructor + * + * @param TagsInterface $client + */ + public function __construct(TagsInterface $client) + { + $this->client = $client; + } + + /** + * Get the HTTP header name that will hold cache tags. + * + * @return string + */ + public function getTagsHeaderName() + { + return $this->client->getTagsHeaderName(); + } + + /** + * Get the value for the HTTP tag header. + * + * This concatenates all tags and ensures correct encoding. + * + * @return string + */ + public function getTagsHeaderValue() + { + return $this->client->getTagsHeaderValue($this->tags); + } + + /** + * Check whether the tag handler has any tags to set on the response. + * + * @return bool True if this handler will set at least one tag. + */ + public function hasTags() + { + return 0 < count($this->tags); + } + + /** + * Add tags to be set on the response. + * + * This must be called before any HTTP response is sent to the client. + * + * @param array $tags List of tags to add. + * + * @return $this + */ + public function addTags(array $tags) + { + $this->tags = array_merge($this->tags, $tags); + + return $this; + } +} diff --git a/tests/Functional/CacheInvalidatorTest.php b/tests/Functional/CacheInvalidatorTest.php index 4cfd12fa..9fa07376 100644 --- a/tests/Functional/CacheInvalidatorTest.php +++ b/tests/Functional/CacheInvalidatorTest.php @@ -12,7 +12,6 @@ namespace FOS\HttpCache\Tests\Functional; use FOS\HttpCache\CacheInvalidator; -use FOS\HttpCache\Handler\TagHandler; use FOS\HttpCache\Test\VarnishTestCase; /** @@ -23,12 +22,11 @@ class CacheInvalidatorTest extends VarnishTestCase public function testInvalidateTags() { $cacheInvalidator = new CacheInvalidator($this->getProxyClient()); - $tagHandler = new TagHandler($cacheInvalidator); $this->assertMiss($this->getResponse('/tags.php')); $this->assertHit($this->getResponse('/tags.php')); - $tagHandler->invalidateTags(['tag1']); + $cacheInvalidator->invalidateTags(['tag1']); $cacheInvalidator->flush(); $this->assertMiss($this->getResponse('/tags.php')); diff --git a/tests/Unit/CacheInvalidatorTest.php b/tests/Unit/CacheInvalidatorTest.php index bbf558d9..0799914e 100644 --- a/tests/Unit/CacheInvalidatorTest.php +++ b/tests/Unit/CacheInvalidatorTest.php @@ -17,7 +17,6 @@ use FOS\HttpCache\Exception\ProxyResponseException; use FOS\HttpCache\Exception\ProxyUnreachableException; use FOS\HttpCache\Exception\UnsupportedProxyOperationException; -use FOS\HttpCache\Handler\TagHandler; use FOS\HttpCache\ProxyClient\Varnish; use Http\Adapter\Exception\HttpAdapterException; use \Mockery; @@ -34,6 +33,7 @@ public function testSupportsTrue() $this->assertTrue($cacheInvalidator->supports(CacheInvalidator::PATH)); $this->assertTrue($cacheInvalidator->supports(CacheInvalidator::REFRESH)); $this->assertTrue($cacheInvalidator->supports(CacheInvalidator::INVALIDATE)); + $this->assertTrue($cacheInvalidator->supports(CacheInvalidator::TAGS)); } public function testSupportsFalse() @@ -45,6 +45,7 @@ public function testSupportsFalse() $this->assertFalse($cacheInvalidator->supports(CacheInvalidator::PATH)); $this->assertFalse($cacheInvalidator->supports(CacheInvalidator::REFRESH)); $this->assertFalse($cacheInvalidator->supports(CacheInvalidator::INVALIDATE)); + $this->assertFalse($cacheInvalidator->supports(CacheInvalidator::TAGS)); } /** @@ -108,6 +109,23 @@ public function testInvalidate() $cacheInvalidator->invalidate($headers); } + public function testInvalidateTags() + { + $tags = [ + 'post-8', + 'post-type-2' + ]; + + $tagHandler = \Mockery::mock('\FOS\HttpCache\ProxyClient\Invalidation\TagsInterface') + ->shouldReceive('invalidateTags') + ->with($tags) + ->once() + ->getMock(); + + $cacheInvalidator = new CacheInvalidator($tagHandler); + $cacheInvalidator->invalidateTags($tags); + } + public function testInvalidateRegex() { $ban = \Mockery::mock('\FOS\HttpCache\ProxyClient\Invalidation\BanInterface') @@ -148,6 +166,12 @@ public function testMethodException() } catch (UnsupportedProxyOperationException $e) { // success } + try { + $cacheInvalidator->invalidateTags([]); + $this->fail('Expected exception'); + } catch (UnsupportedProxyOperationException $e) { + // success + } } /** diff --git a/tests/Unit/Handler/TagHandlerTest.php b/tests/Unit/Handler/TagHandlerTest.php deleted file mode 100644 index d8f5dd29..00000000 --- a/tests/Unit/Handler/TagHandlerTest.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\HttpCache\Tests\Unit\Handler; - -use FOS\HttpCache\CacheInvalidator; -use FOS\HttpCache\Handler\TagHandler; - -class TagHandlerTest extends \PHPUnit_Framework_TestCase -{ - public function testInvalidateTags() - { - $cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator') - ->shouldReceive('invalidate') - ->with(['X-Cache-Tags' => '(post\-1|posts)(,.+)?$']) - ->once() - ->shouldReceive('supports') - ->with(CacheInvalidator::INVALIDATE) - ->once() - ->andReturn(true) - ->getMock(); - - $tagHandler = new TagHandler($cacheInvalidator); - $tagHandler->invalidateTags(['post-1', 'posts']); - } - - public function testInvalidateTagsCustomHeader() - { - $cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator') - ->shouldReceive('invalidate') - ->with(['Custom-Tags' => '(post\-1)(,.+)?$']) - ->once() - ->shouldReceive('supports') - ->with(CacheInvalidator::INVALIDATE) - ->once() - ->andReturn(true) - ->getMock(); - - $tagHandler = new TagHandler($cacheInvalidator, 'Custom-Tags'); - $this->assertEquals('Custom-Tags', $tagHandler->getTagsHeaderName()); - $tagHandler->invalidateTags(['post-1']); - } - - public function testEscapingTags() - { - $cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator') - ->shouldReceive('invalidate') - ->with(['X-Cache-Tags' => '(post_test)(,.+)?$']) - ->once() - ->shouldReceive('supports') - ->with(CacheInvalidator::INVALIDATE) - ->once() - ->andReturn(true) - ->getMock(); - - $tagHandler = new TagHandler($cacheInvalidator); - $tagHandler->invalidateTags(['post,test']); - } - - /** - * @expectedException \FOS\HttpCache\Exception\UnsupportedProxyOperationException - */ - public function testInvalidateUnsupported() - { - $cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator') - ->shouldReceive('supports') - ->with(CacheInvalidator::INVALIDATE) - ->once() - ->andReturn(false) - ->getMock(); - - new TagHandler($cacheInvalidator); - } - - public function testTagResponse() - { - $cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator') - ->shouldReceive('supports') - ->with(CacheInvalidator::INVALIDATE) - ->once() - ->andReturn(true) - ->getMock(); - - $tagHandler = new TagHandler($cacheInvalidator); - $this->assertFalse($tagHandler->hasTags()); - $tagHandler->addTags(['post-1', 'test,post']); - $this->assertTrue($tagHandler->hasTags()); - $this->assertEquals('post-1,test_post', $tagHandler->getTagsHeaderValue()); - } -} diff --git a/tests/Unit/ProxyClient/VarnishTest.php b/tests/Unit/ProxyClient/VarnishTest.php index 220b5509..e43fafb7 100644 --- a/tests/Unit/ProxyClient/VarnishTest.php +++ b/tests/Unit/ProxyClient/VarnishTest.php @@ -13,6 +13,7 @@ use FOS\HttpCache\ProxyClient\Varnish; use FOS\HttpCache\Test\HttpClient\MockHttpAdapter; +use Psr\Http\Message\RequestInterface; use \Mockery; class VarnishTest extends \PHPUnit_Framework_TestCase @@ -99,6 +100,43 @@ public function testBanPathEmptyHost() $varnish->banPath('/articles/.*', 'text/html', $hosts); } + public function testTagsHeaders() + { + $varnish = new Varnish(['127.0.0.1:123'], 'fos.lo', $this->client); + $varnish->setDefaultBanHeaders( + ['A' => 'B'] + ); + $varnish->setDefaultBanHeader('Test', '.*'); + $varnish->invalidateTags(['post-1', 'post-type-3'])->flush(); + + $requests = $this->getRequests(); + + $this->assertCount(1, $requests); + $this->assertEquals('BAN', $requests[0]->getMethod()); + + $this->assertEquals('(post\-1|post\-type\-3)(,.+)?$', $requests[0]->getHeaderLine('X-Cache-Tags')); + $this->assertEquals('fos.lo', $requests[0]->getHeaderLine('Host')); + + // That default BANs is taken into account also for tags as they are powered by BAN in this client. + $this->assertEquals('.*', $requests[0]->getHeaderLine('Test')); + $this->assertEquals('B', $requests[0]->getHeaderLine('A')); + } + + + public function testTagsHeadersEscapingAndCustomHeader() + { + $varnish = new Varnish(['127.0.0.1:123'], 'fos.lo', $this->client, 'X-Tags-TRex'); + $varnish->invalidateTags(['post-1', 'post,type-3'])->flush(); + + $requests = $this->getRequests(); + + $this->assertCount(1, $requests); + $this->assertEquals('BAN', $requests[0]->getMethod()); + + $this->assertEquals('(post\-1|post_type\-3)(,.+)?$', $requests[0]->getHeaderLine('X-Tags-TRex')); + $this->assertEquals('fos.lo', $requests[0]->getHeaderLine('Host')); + } + public function testPurge() { $ips = ['127.0.0.1:8080', '123.123.123.2']; diff --git a/tests/Unit/ResponseTaggerTest.php b/tests/Unit/ResponseTaggerTest.php new file mode 100644 index 00000000..15b5a4ec --- /dev/null +++ b/tests/Unit/ResponseTaggerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\HttpCache\Tests\Unit; + +use FOS\HttpCache\ResponseTagger; + +class ResponseTaggerTest extends \PHPUnit_Framework_TestCase +{ + public function testTagResponse() + { + $proxyClient = \Mockery::mock('FOS\HttpCache\ProxyClient\Invalidation\TagsInterface') + ->shouldReceive('getTagsHeaderValue') + ->with(['post-1', 'test,post']) + ->once() + ->andReturn('post-1,test_post') + ->getMock(); + + $tagHandler = new ResponseTagger($proxyClient); + $this->assertFalse($tagHandler->hasTags()); + $tagHandler->addTags(['post-1', 'test,post']); + $this->assertTrue($tagHandler->hasTags()); + $this->assertEquals('post-1,test_post', $tagHandler->getTagsHeaderValue()); + } +}