The Cache API is a powerful feature of Drupal, but it might be hard to figure out how to use it correctly. Over time I have seen different ways to do it, but I believe there is a method better than others.
The benefits of using a cache are probably clear for all developers. It's important to use it correctly, or you run into problems where content is not updating as it should and requires global cache rebuild, which can be expensive and annoying to remember to do it.
I have selected the hook hook_ENTITY_TYPE_view_alter()
to show how to do it inside hooks that work with render arrays. This example implements an alter hook for a paragraph bundle, which renders custom entities based on the selected categories. When you skip collecting cache info here, the paragraph isn't updating when the category is renamed or deleted and fails to show changes related to custom entities.
You could use the ENTITY_TYPE_list
cache tag here, but it reacts to all changes and is probably something you don't want. That means we need something more granular. We can put together an array of the cache tags and use Cache::mergeTags()
to put things together, but it's a bit messy. We should also merge context and max-age, there might be differences from the defaults. It probably needs additional checks because parameters need to be arrays, but it's possible that the default render array doesn't have any values set yet.
There is a utility class for handling cache information more easily - CacheableMetadata. It has two static methods to use - creating a new metadata object from the render array or an object. For this example, I will use CacheableMetadata::createFromRenderArray()
where I use a render array, that has #cache
properties already included. This is for preserving already provided information because at the end we use CacheableMetadata::applyTo()
method, which overwrites cache data with the data inside the object.
To add additional cache data, it is possible to use CacheableMetadata::addCacheableDependency()
for objects and CacheableMetadata::addCacheTags()
(methods for context and max-age are available) for adding additional tags directly. These methods make it easy and cleaner to add additional cache metadata. Below is a code example of how you can use it.
/**
* Implements hook_ENTITY_TYPE_view_alter().
*/
function my_module_paragraph_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
if ($entity->bundle() !== 'my_paragraph') {
return;
}
// Load existing cache data from render array.
$cache_metadata = CacheableMetadata::createFromRenderArray($build);
$build['categories'] = [];
/** @var \Drupal\taxonomy\Entity\Term[] $categories */
$categories = $entity->get('field_my_paragraph_categories')->referencedEntities();
foreach ($categories as $category) {
// Add term cache data.
$cache_metadata->addCacheableDependency($category);
$my_entity_ids = \Drupal::entityQuery('my_entity')
->accessCheck()
->condition('my_entity_category', $category->id())
->execute();
$my_entities = \Drupal::entityTypeManager()->getStorage('my_entity')->loadMultiple($my_entity_ids);
foreach ($my_entities as $my_entity) {
// Add my entity metadata.
$cache_metadata->addCacheableDependency($my_entity);
}
}
// Custom cache tags.
$cache_metadata->addCacheTags(['config:views.view.my_view']);
// Add cache dara back to render array.
$cache_metadata->applyTo($build);
}
Correctly implemented caching logic can reduce future problems and issues. You should consider it in the planning and code-writing stages, and QA at the end should also check for potential problems.
Caching is a broad topic in Drupal, and there are many caveats and not so known things that I can write about in later articles.