Understanding Drupal cache contexts via history and code
The Drupal cache system is just a key-value store. Say, your key is “left sidebar”, the value is the HTML of the left sidebar. Very simple. But if the left sidebar contained the login block, then you have a different HTML string for anonymous and authenticad users. So now you have a cache ID “left sidebar for anonymous users” pointing to one piece of HTML and “left sidebar for logged in users” pointing to another. Of course Drupal is multilingual, so you will have “left sidebar for anonymous users in English” and “left sidebar for logged in users in French” and so on. Maybe you had customizable sidebars per users so now you had a different cache entry per users. Quite obviously many cached pieced of content changes on every page. And this was what Drupal 7 offered: the cache ID had parts set per the caller (say, “block” and “left_sidebar”) and then drupal_render_cid_parts added parts creating different cache IDs per role, per user, per language, per page. So’d have say “block:left_sidebar:bartik:en:fr:u1234”.
In Drupal 8, there is a much bigger flexibility. Here’s an actual cache id (truncated):
entity_view:block:alexandria_breadcrumbs:[languages:language_content]=de:[languages:language_interface]=de:[languages:language_url]=de:[route]=entity.node.canonical65a0811
The first few parts are just the same: we have a block. But then we see a very big difference: the [languages:language_content]=de
part has an identifier and a value. This is a big advantage compared to the previous system where you’d only have de
and basically hoped noone will manually introduce such a part causing massive confusion. And this is not all hardwired. There’s a service called cache_context.languages
tagged with cache.context
which implements the CacheContextInterface
and the getContext()
method will return the language depending on the type — all three languages present in the cache ID are calculated per the same method. Finally we see a route
context. As you can guess, there’s a cache_context.route
service, again tagged with cache.context
and the getContext method returns the hashed route parameters appended to the route name. So if you are on a
different page, the system will end up with a different cache id and so the cached content will vary per page.
Say, you have a block which is different per node type. It would be much more beneficial to solve this problem a bit more generic — let’s write a cache context which allows different blocks per the value of a field. The getContext()
method is nothing more than just retrieving the entity from the route match and then converting the value of a field to a string:
public function getContext($entity_type = NULL, $field_name = NULL) {
$entity = $this->routeMatch->get($entity_type);
if ($entity instanceof FieldableEntityInterface && $entity->hasField($field_name)) {
return hash('sha256', serialize($entity->get($field_name)->getValue()));
}
return '';
}
this could be used as the entity:node:type
cache context, for example.