-
-
Notifications
You must be signed in to change notification settings - Fork 848
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
Support for easier registration of own cache purgers #1689
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Definitively a good improvement.
@@ -498,6 +498,12 @@ private function registerHttpCache(ContainerBuilder $container, array $config, X | |||
|
|||
$loader->load('http_cache_tags.xml'); | |||
|
|||
// TODO: Decide on how you would like to configure whether to use Varnish or Symfony (or any other?) | |||
if (0 == count($config['http_cache']['invalidation']['varnish_urls'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may allow to use both (to avoid such ifs). If a varnish_urls
is defined, it's definitely to use Varnish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So how would your preferred config look like? If varnish_urls
then Varnish, else Symfony? What if we want to integrate another one?
// Encode tags for greater compatiblity with different proxies | ||
// Some do not allow special characters like / or @ in cache tags and | ||
// also it allows to use a , in a tag, if you wish to do so. | ||
$resources = array_map(function($resource) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were using md5 initially, we switched back to plain text because it is very hard to debug when reading/grepping logs. I would prefer to encode special char at the cache adapter layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do that but to remain BC I need to introduce another interface for the Purgers that implement both PurgerInterface and...hmmm... CacheTagsFormatterInterface?
@@ -0,0 +1,36 @@ | |||
<?php |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this interface and the related trait? Can't we just use inject the PSR-6 adapter in the purger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No that's not possible. HttpCache
works outside the application so you cannot access it from within the container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could implement the same as Varnish does. Have the purger execute a PURGE
request and have a kernel.request
listener that purges the data. But that's way too complicated I find.
src/HttpCache/SymfonyPurger.php
Outdated
|
||
namespace ApiPlatform\Core\HttpCache; | ||
|
||
use Toflar\Psr6HttpCacheStore\Psr6StoreInterface; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be possible to move this new implementation directly in Symfony?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I tried and it was denied. See symfony/symfony#20061 for the whole discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok... I'm not confident with adding (soft) dependencies to non-popular yet libraries (like Psr6HttpCacheStore
). We already had problems with bridges to 3rd party libraries not very well maintained (FOS things...).
My proposal is:
- Tweak the config in this PR to allow to easily register external Purgers from the config (using a service name for instance).
- Move the
SymfonyPurger
class and the trait in an external bundle (or directly inPsr6HttpCacheStore
) for now
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I understand. In that case, it's my library and it consists of one class so I can basically guarantee it's maintained because I can't think of a lot of stuff that would need to be maintained there 😄 But anyway, I just feel it can never get popular if we don't give it the chance. I mean, I'm totally okay with putting the APIP integration into it's own bundle if you don't want it in core but then it will be just another one floating around in my personal namespace instead of APIP so chances are, people won't even notice there's such a thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mostly a docs issue to me. I'm totally ok to add a new doc entry explaining how to install your lib (that will be required to install manually anyway) to get cache invalidation in pure PHP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Kernel should be in whatever namespace.
If the purger is implementing our PurgerInterface yeah it can be in a Bridge/ApiPlatform thingy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, no problem. There's one thing that needs to be clarified first though: the encoding of the tags. I see two options here:
-
I add a new interface every purger can implement that allows to ask the purger to encode the tags whereas fallback would be the current, comma-separated list. Pros: Very flexible, Cons: more complicated.
-
We use
md5
orsha1
by default and add aCache-Tags-Debug
header which shows the raw tags ifkernel.debug
is enabled. I cannot think of any cache implementation that cannot handle hashes as keys. Pros: Simple, Cons: Less flexible.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd go for option 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, would you still add the additional debug header? I think it might be very useful and it doesn't harm because it's hidden in production anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I think it's a good idea as well!
80d3ac0
to
8158d69
Compare
8158d69
to
149b857
Compare
Changed title, description, implementation, rebased on master branch. Ready for review round 2 😄 |
@@ -498,6 +498,14 @@ private function registerHttpCache(ContainerBuilder $container, array $config, X | |||
|
|||
$loader->load('http_cache_tags.xml'); | |||
|
|||
// Load purger | |||
$purger = $config['http_cache']['invalidation']['purger']; | |||
$container->setAlias('api_platform.http_cache.purger', 'api_platform.http_cache.purger.'.$purger); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use "api_platform.http_cache.purger.$purger"
to trigger https://blog.blackfire.io/php-7-performance-improvements-encapsed-strings-optimization.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice one! I've read a lot about opcode optim but must have missed that one! Done in f4aec2e.
|
||
public function __construct(IriConverterInterface $iriConverter) | ||
public function __construct(IriConverterInterface $iriConverter, PurgerInterface $purger, bool $debug = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just typehint CacheTagsFormattingPurgerInterface
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No because the current VarnishPurger
does not implement that and I don't know what other implementations have been made out there. Also it's completely optional to adjust the formatting of the tags :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get it. The dependency wasn't existent before. CacheTagsFormatterInterface $formatter = null
will do the trick. Btw we try to avoid setter injection as much as possible.
* | ||
* @author Yanick Witschi <yanick.witschi@terminal42.ch> | ||
*/ | ||
interface CacheTagsFormattingPurgerInterface extends PurgerInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about not extending PurgerInterface
and renaming it CacheTagsFormatterInterface
? It will allow to extract this logic in a separated class if necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thought about that too but then again, where would you put that? It should not reside in the HttpCache
namespace etc. so I thought I'm going to create a very specific interface for that purpose only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not in HttpCache
? This namespace looks good to me.
|
||
public function __construct(IriConverterInterface $iriConverter) | ||
public function __construct(IriConverterInterface $iriConverter, PurgerInterface $purger, bool $debug = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make the purger (formatter) optional? It's useless when using the builtin one (it eases to use classes outside of Symfony / API Platform full stack). + it's a BC break
@@ -66,6 +73,16 @@ public function onKernelResponse(FilterResponseEvent $event) | |||
return; | |||
} | |||
|
|||
$event->getResponse()->headers->set('Cache-Tags', implode(',', $resources)); | |||
if ($this->debug) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it is worth it. The Formatter can do it by itself by using RequestStack
(and it's useless for the default purger).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, even in your purger, I suggest to emit non-encoded headers, and to do the encoding/decoding at the cache store level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just an easy debugging help without any negative side effects.
It's the Symfony Cache that does not allow certain characters, that comment should probably go there :-) I still think it's fine here. Sometimes you don't have control over the cache internals and then you're lost without that. I will make both optional using setter injection though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to insist but please drop it, it brings nothing for the default setup (cache tags are already in clear text) and may cause maintenance issues (I don't like injecting the debug
parameter in this listener). It can easily be added in an external class/bundle.
f2fdacb
to
24b7f18
Compare
PR is ready for another review then :) |
|
||
public function __construct(IriConverterInterface $iriConverter) | ||
public function __construct(IriConverterInterface $iriConverter, CacheTagsFormattingInterface $purger = null, bool $debug = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$purger
as variable name does not seem appropriate here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, fixed in a1b08de
* | ||
* @author Yanick Witschi <yanick.witschi@terminal42.ch> | ||
*/ | ||
interface CacheTagsFormattingInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CacheTaggingFormatterInterface
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's correct. The implementation of that interface "formats the cache tags". So "CacheTagsFormatting" is correct. "CacheTaggingFormatter" sounds really weird to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"formats the cache tags"
So... What do you think of CacheTagsFormatterInterface
? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed 😄
@@ -207,6 +207,7 @@ public function getConfigTreeBuilder() | |||
->info('Enable the tags-based cache invalidation system.') | |||
->canBeEnabled() | |||
->children() | |||
->scalarNode('purger')->defaultValue('varnish')->cannotBeEmpty()->info('The name of the purger to use.')->end() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it should be directly the name of the service instead of a tricky concatenation (i.e api_platform.http_cache.purger.$purger
). Or did I miss something specific?
So, create an implementation of your new interface for Varnish whose the service name will be the default value here.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 2e5bbbf.
2e5bbbf
to
0b80aa7
Compare
Rebased onto master. |
02b5c40
to
ebf4369
Compare
|
||
// BC | ||
if (null === $this->tagsFormatter) { | ||
@trigger_error('Passing no implementation of the CacheTagsFormatterInterface is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't trigger a deprecation notice here. It makes it header to use for no benefit.
{ | ||
$this->iriConverter = $iriConverter; | ||
$this->tagsFormatter = $tagsFormatter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$this->tagsFormatter = $tagsFormatter ?? new CsvFormatter();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even better:
$this->tagsFormatter = $tagsFormatter ?? function (array $iris): string
{
return implode(',', $iris);
};
* | ||
* @author Yanick Witschi <yanick.witschi@terminal42.ch> | ||
*/ | ||
class CsvFormatter implements CacheTagsFormatterInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm think it is not worth it to add a new class for an implode (see my other suggestion).
* | ||
* @author Yanick Witschi <yanick.witschi@terminal42.ch> | ||
*/ | ||
interface CacheTagsFormatterInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about just using callable
. For this use case it's enough IMO, no need to introduce an interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callables are not a good choice at all here. They are very hard to add. Right now you can simply configure a different service by using the tags_formatter
config setting and you're done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting tags is a very specific edge case to me, ok to allow to do it, but I'm not confortable to add so many things just for this. A callable
looks perfect for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always said it's something that's specific to the purger because the purger knows how to format the tags so it can actually purge them. It never makes sense to arbitrarily combine purgers and tag formatters. That's why I had interface CacheTagsFormattingPurgerInterface extends PurgerInterface
and passed the purger to the AddTagsListener
. That way, any purger can format if it needs but it can also just leave this interface alone and go with the default implementation which is just a CSV list. This is still the version that makes the most sense from a software architectural POV to me because responsibilities are put where they belong.
@@ -66,6 +73,16 @@ public function onKernelResponse(FilterResponseEvent $event) | |||
return; | |||
} | |||
|
|||
$event->getResponse()->headers->set('Cache-Tags', implode(',', $resources)); | |||
if ($this->debug) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to insist but please drop it, it brings nothing for the default setup (cache tags are already in clear text) and may cause maintenance issues (I don't like injecting the debug
parameter in this listener). It can easily be added in an external class/bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see the comments
I've put the formatting into it's own class because of the discussion in Slack. I don't really feel like changing it yet again. |
Debugging is gone. |
…ers and improved debug output
… injection instead of setter injection
6f046ed
to
31fea1a
Compare
I can finish this if you want @Toflar |
No, it will just have to wait for after my holidays. And callables are the wrong choice to me 😄 |
|
||
// BC | ||
if (null === $this->tagsFormatter) { | ||
@trigger_error('Passing no implementation of the CacheTagsFormatterInterface is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing an object that does not implement CacheTagsFormatterInterface is deprecated since version 2.3 and will be removed in 3.0.
WDYT ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
Could you please update with master ;).
I would like to rework it completely as outlined in #1776. |
But haven't gotten any feedback there yet. |
@Toflar ok for me! |
Should I do something with this @Toflar ? |
I think that can be closed. I've worked a lot on the FOSCache lib and bundle lately and I found that it would be better to tie tagging and purging to the same service so it would require a complete overhaul anyway. |
Thanks for the quick response @Toflar ! |
This will make it pretty easy to implement and register custom cache purgers as follows:
This will then alias the
api_platform.http_cache.purger
tomy_superb_cache_purger
.In addition to this, I've introduced a
CacheTagsFormatterInterface
that allows to delegate the formatting of the$iris
to the purger and aCache-Tags-Debug
header that contains them unformatted in casekernel.debug
is set totrue
.