Namespace-based cache invalidation is a technique where cache keys are grouped under a logical namespace (e.g. per user, locale, or entity). Instead of deleting individual keys, you invalidate the entire namespace, effectively removing all related entries at once.
In Symfony 7.3, we're adding namespace-based cache invalidation to all cache
adapters in the Cache component. This is implemented via the new
Symfony\Contracts\Cache\NamespacedPoolInterface
, which defines the
withSubNamespace()
method:
1 2 3 4 5 6 7
$subCache = $cache->withSubNamespace('foo');
$subCache->get('my_cache_key', function (ItemInterface $item): string {
$item->expiresAfter(3600);
return '...';
});
In this example, the cache item uses the my_cache_key
key, but it's stored
internally under the foo
namespace. This is handled transparently, so you
don't need to manually prefix keys like foo.my_cache_key
. In practice,
this groups cache items logically into different sets:
1 2 3 4 5 6 7 8 9 10 11 12 13
$subCache = $cache->withSubNamespace('foo');
$bar1 = $cache->getItem('bar1');
$bar1->set(...); $cache->save();
$bar2 = $subCache->getItem('bar2');
$bar2->set(...); $subCache->save();
$cache->getItem('bar1')->isHit(); // true
$cache->getItem('bar2')->isHit(); // false
$subCache->getItem('bar1')->isHit(); // false
$subCache->getItem('bar2')->isHit(); // true
$subCache->getItem('foo.bar2')->isHit(); // false
Namespaces can be anything that makes sense in your application. For example, you can cache data per user, per locale, or per entity:
1 2 3
$userCache = $cache->withSubNamespace((string) $userId);
$localeCache = $cache->withSubNamespace($request->getLocale());
$productCache = $cache->withSubNamespace($productId);
This looks awesome! In this blog post and the documentation you write "This is handled transparently, so you don't need to manually prefix keys like foo.my_cache_key".
But there is no example what happens if you try to do it. Would it find an item in the subCache if you call
$cache->getItem('foo.bar2)->isHit()
on the main cache? Maybe you want to add this example to the docs and the blog post, thanks!@Rafael namespaced keys cannot be accessed from the main cache, because the storage is not done by prefixing the PSR-16 key with the namespace followed by a dot. It requires getting a subcache instance for the "foo" namespace. The cache key in the underlying storage uses a delimiter that is not allowed in valid PSR-16 cache keys (most adapters use the colon for that, but they are free to use the delimiter they want in case there is a common convention for that storage). The only exception right now is the case of the Psr16Adapter wrapping an arbitrary PSR-16 implementation (to adapt one that is not from symfony/cache), as it has to keep generating valid PSR-16 keys for the underlying PSR-16 storage.
This means that you cannot have key conflicts between a namespaced cache and its parent cache.
Does it effectively create a new cache pool? Is it possible to use the cache:pool:... cli commands to invalidate or delete items or the whole sub-namespace?