Skip to content

addTags on TagHandler #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/_static/fos.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.versionmodified {
font-weight: bold;
}
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
def setup(app):
app.add_javascript('tabs.js')
app.add_stylesheet('tabs.css')
app.add_stylesheet('fos.css')

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
Expand Down
32 changes: 27 additions & 5 deletions doc/invalidation-handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ to simplify common operations.
Tag Handler
-----------

The tag handler helps you to invalidate all cache entries that where marked
with a specified tag. It works only with a ``CacheInvalidator`` that supports
``CacheInvalidator::INVALIDATE``.
.. versionadded:: 1.3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not render as nicely as expected, because our sphinx setup does not load basic.css but only theme.css - @ddeboer would you know why we do not load default.css and what i could do to get the

.versionmodified {
    font-style: italic;
}

into our css, or load basic.css?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the Read the Docs theme (theme.css) on local builds too, so you can see what it will really look like. Unfortunately, they don’t support the version directives. Pushed a commit that adds some basic formatting for versionadded/versionchanged.

The tag handler was added in FOSHttpCache 1.3. If you are using an older
version of the library and can not update, you need to use
``CacheInvalidator::invalidateTags``.

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
~~~~~
Expand All @@ -34,8 +39,22 @@ 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 by setting the ``X-Cache-Tags`` header. The
FOSHttpCacheBundle_ does this for you when you’re using Symfony.
correct tags on all responses. You can add tags to the handler using::

$tagHandler->addTags(array('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:

Expand Down Expand Up @@ -74,3 +93,6 @@ header ``X-Cache-Tags`` in the constructor::

Make sure to reflect this change in your
:doc:`caching proxy configuration <proxy-configuration>`.

.. _header: http://php.net/header
.. _additional methods of defining tags: http://foshttpcachebundle.readthedocs.org/en/latest/features/tagging.html
6 changes: 3 additions & 3 deletions src/CacheInvalidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public function addSubscriber(EventSubscriberInterface $subscriber)
*/
public function setTagsHeader($tagsHeader)
{
if ($this->tagHandler && $this->tagsHeader !== $tagsHeader) {
if ($this->tagHandler && $this->tagHandler->getTagsHeaderName() !== $tagsHeader) {
$this->tagHandler = new TagHandler($this, $tagsHeader);
}

Expand All @@ -172,11 +172,11 @@ public function setTagsHeader($tagsHeader)
*
* @return string
*
* @deprecated Use TagHandler::getTagsHeader instead.
* @deprecated Use TagHandler::getTagsHeaderName instead.
*/
public function getTagsHeader()
{
return $this->tagsHeader;
return $this->tagHandler ? $this->tagHandler->getTagsHeaderName() : $this->tagsHeader;
}

/**
Expand Down
57 changes: 56 additions & 1 deletion src/Handler/TagHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class TagHandler
*/
private $tagsHeader;

/**
* @var array
*/
private $tags = array();

/**
* Constructor
*
Expand All @@ -49,6 +54,40 @@ public function __construct(CacheInvalidator $invalidator, $tagsHeader = 'X-Cach
$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)));
}

/**
* Add tags to be sent.
*
* This must be called before any response is sent to the client.
*
* @param array $tags List of tags to add.
*/
public function addTags(array $tags)
{
$this->tags = array_merge($this->tags, $tags);
}

/**
* Invalidate cache entries that contain any of the specified tags in their
* tag header.
Expand All @@ -59,10 +98,26 @@ public function __construct(CacheInvalidator $invalidator, $tagsHeader = 'X-Cach
*/
public function invalidateTags(array $tags)
{
$tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $tags)));
$tagExpression = sprintf('(%s)(,.+)?$', implode('|', array_map('preg_quote', $this->escapeTags($tags))));
$headers = array($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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think protected makes sense here, as an extending class might want to use this for other operations. or a special setup might need additional escaping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

{
array_walk($tags, function (&$tag) {
$tag = str_replace(array(',', "\n"), array('_', '_'), $tag);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we missed doing this before, leading to , in a tag breaking the tagging. not sure if we need \n or not

});

return $tags;
}
}
31 changes: 31 additions & 0 deletions tests/Unit/Handler/TagHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,26 @@ public function testInvalidateTagsCustomHeader()
->getMock();

$tagHandler = new TagHandler($cacheInvalidator, 'Custom-Tags');
$this->assertEquals('Custom-Tags', $tagHandler->getTagsHeaderName());
$tagHandler->invalidateTags(array('post-1'));
}

public function testEscapingTags()
{
$cacheInvalidator = \Mockery::mock('FOS\HttpCache\CacheInvalidator')
->shouldReceive('invalidate')
->with(array('X-Cache-Tags' => '(post_test)(,.+)?$'))
->once()
->shouldReceive('supports')
->with(CacheInvalidator::INVALIDATE)
->once()
->andReturn(true)
->getMock();

$tagHandler = new TagHandler($cacheInvalidator);
$tagHandler->invalidateTags(array('post,test'));
}

/**
* @expectedException \FOS\HttpCache\Exception\UnsupportedProxyOperationException
*/
Expand All @@ -62,4 +79,18 @@ public function testInvalidateUnsupported()

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);
$tagHandler->addTags(array('post-1', 'test,post'));
$this->assertEquals('post-1,test_post', $tagHandler->getTagsHeaderValue());
}
}