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):


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.

June 5, 2020

Previous post
Let’s learn recursive CTE SQL via paragraphs We are going through a partial relaunch and it came up twice to find the nodes which have a certain paragraph somewhere. The first one was just a
Next post
Writing the tranc module We hit Content and interface translation don’t clearly separate. I set out to fix it for ourselves and then released it back. It’s possible the