Enhancing the Caching Framework adopting the CacheContract Pattern (like Symfony)

Having worked a lot with the Caching Framework we all use, and held talks about it, I would like to propose to make some changes to the current CF in order to improve cache handling in the future

Status Quo
the current pattern to cache some content (VariableFrontend) is the following:

$cF = GeneralUtility::makeInstance(CacheManager::class)->getCache('identifier');
if (! $content = $cF->get($key)) {
  // do expensive stuff and write it to $content
  $cF->set($key,$content,['tag1','tag2'],86400);
}
return $content;

Proposal
My proposal would be to extend CacheManager::get with a Closure as an optional second parameter. This is called the CacheContract Pattern Symfonys implementation

This could look like this:

$cF = GeneralUtility::makeInstance(CacheManager::class)->getCache('identifier');
$content = $cF->get($key,function($key, $cF) use ($var1,$var2) {
  // do expensive stuff and write it to $content
  $cF->set($key,$content,['tag1','tag2'],86400);
  return $content;
});
return $content;

This is a bit different than Symfony does it, but this way the ‘set’ method and the configuration, tag handling, timeout handling could stay the same, making the transition from the old pattern to the new pattern easier.

The get method would simply call the closure - if given - if a cache could not be retrieved, and returning the content as it did before. The changes to the Interface would be minimal and stay backwards compatible.

The old pattern could stay valid in those cases were it could be undesirable to have stale caches (backend?)

Advantages to this approach
There would not be a direct advantage, but once this is implemented the Caching Framework could be enhanced to have a more dynamic approach to cache invalidation and recreation.

If a Cache has timed out the first call to recreate this content could mark the cache as stale. Subsequent calls to the same cache-key would then still deliver the stale cache for a short while, and the first call could take its time to recreate the cached item.

As well cache-clear could mark caches as stale and to-be-recreated rather than expunging them directly, giving the chance for a single recreation.

On Sites with high load and lots of traffic this would have several advantages.

With our current pattern this is impossible to optimize.

Disadvantages
Don’t know? Maybe a CacheGenerator Class implemented by the extension developer would be a better approach. Maybe both could be done…?
It would not be PSR-6, but there has not been much updates, and personally I find this pattern a better solution.

Problems
one would have to check how this can benefit caches created via typoscript and other frontends. My work usually only required the VariableFrontend, so I have not looked at the other Frontends yet.

Next steps
Do others see some benefit here as well? Maybe enough for an Initiative?
I would be willing to spend time and effort and code on this.
An Initiative would of course look into implementing this feature in the core and maybe adapting the cores caches and exploring the optimization of the cache handling as outlined

Hey Frank,

thanks for the info. I have used this pattern in EXT:menus and I very much enjoy the developer experience with it.

https://github.com/b13/menus/blob/master/Classes/Compiler/LanguageMenuCompiler.php#L36

Hi Benni,

exactly, once you are used to the pattern it comes natural in my opinion.

I used it in some extensions as well, but I think the core could benefit from the pattern, by giving the CF control over when to actually create the cache or not

I don’t know if this the only way to do it, but I very much want the caching framework to be able to allow for stale caches.

It for sure is not the only way to do it, but it is

a) the closest to the current pattern, so there would be minimal effort to implement it (you might even be able to create a rector for it)

b) the underlying code in the CachingFramework could be adapted with not much effort to start using the pattern, giving room for growing enhancements, without breaking the current pattern

c) not that it is the strongest indicator, but Symfony does it in a similar way