CacheableDependencyInterface & friends
To make dealing with cacheability metadata (cache tags, cache contexts and max-age) easier, Drupal 8 has CacheableDependencyInterface
.
Why?
Imagine having to manually construct the cache tags of every single entity and configuration object you use in a render array (or some other computation) manually. And, when on a multilingual site, also add the necessary cache contexts manually (for either the translated entity or the language override for the configuration object).
And not just entities & configuration, but also access results, block plugins, menu links, context plugins, condition plugins, and so on — because all of those end up in rendering (a specific type of computation) that we would like to cache.
In the early development days of Drupal 8, this used to be the case. Clearly, that was not tenable. And it was very error prone.
That's why CacheableDependencyInterface
was introduced. Like its name indicates: objects implementing this interface can automatically become cacheable dependencies.
For example, when creating a render array that renders to <p>Hi, %user, welcome to %site!</p>
, you rely on both the current user's User
entity and the system.site
configuration. When that render array is cached, it has both that User
entity and that configuration object as its cacheable dependencies.
CacheableDependencyInterface
can be implemented by any value object (i.e. an object that represents a logical unit of data). If you go and look at its API documentation, you'll see it is implemented by a lot of value objects in Drupal 8 core. In fact, it would be safe to say it is implemented by a majority of objects you interact with while writing Drupal 8 code!
There are two extreme opposite cases that are encountered quite frequently, for which Drupal has handy traits: the case of an unchanging object and therefore cacheable forever ( UnchangingCacheableDependencyTrait
, which always returns max-age === permanent
) and the case of an object always being dynamically calculated and therefore never cacheable (UncacheableDependencyTrait
, which always returns max-age === 0
).
RefinableCacheableDependencyInterface
But CacheableDependencyInterface
is only capable of dealing with the "inherent", "canonical" cacheability metadata of an object. Sometimes, there are multiple variants of an object.
The most prominent examples of this are entity translations (it's the same entity, with the same entity ID, just in a different translation) and config translations (it's the same configuration object, with the same configuration name, just with a language override).
In both cases, the cacheability metadata that already exists on the original (untranslated) object remains applicable. For example, node:5
cache tag). But — in case of the entity — the content language cache context is necessary ('languages:' . LanguageInterface::TYPE_CONTENT
, see Cache contexts
), to convey that this entity is a variant of the original entity, that varies depending on whatever content language cache context was negotiated. Similarly, in case of the configuration object, the interface language cache context is necessary ('languages:' . LanguageInterface::TYPE_INTERFACE
), to convey that this configuration object is a variant of the original configuration object, that varies depending on whatever interface language cache context was negotiated.
An example beyond translation would be the Pirate day
module that has a configuration override that applies only on pirate day, which inserts yar
, har
and random parrots in configuration; the configuration objects will then have a pirate_day
cache context.
In all of the above examples, we need to be able to refine the cacheability metadata of our objects, to indicate a variant that has been loaded. For that reason, RefinableCacheableDependencyInterface
was added, which allows just that: it has the ability to add cache tags, contexts and update the max-age.
To make it easy to implement this interface, there's also a handy trait: RefinableCacheableDependencyTrait
.
About entities & configuration objects
All entities in Drupal 8 (core, contrib & custom) implement the EntityInterface
interface which extends both CacheableDependencyInterface
and RefinableCacheableDependencyInterface
. Besides that, all entities in Drupal 8 core extend the Entity
abstract base class, and contrib/custom are encouraged to do the same. This means that every single entity you interact with in Drupal 8 automatically has consistent cache tags (of the form <entity type>:<entity ID>
, e.g. node:5
and user:3
) and cache contexts to reflect the translation.
All configuration objects in Drupal 8 core extend the ConfigBase
abstract base class, which implements both CacheableDependencyInterface
and RefinableCacheableDependencyInterface
. This means that every single configuration object you interact with in Drupal 8 automatically has consistent cache tags (of the form config:<configuration name>
, e.g. config:system.performance
) and cache contexts to reflect the config overrides (of which translation is the only example in core).
Finally, all entities and configuration objects in Drupal 8 automatically have the content/interface language cache contexts (respectively) thanks to Entitymanager::getTranslationFromContext()
and LangaugeConfigFactoryOverride::getCacheableMetadata($name)
.
Using objects that are cacheable dependencies
Rendering is the most common example of depending on an object that is a cacheable dependency. To simplify that, we have RendererInterface::addCacheableDependency($build, $dependency)
— where $build
is the render array that depends on the object $dependency
; the cacheability metadata of the object will automatically be "absorbed" by the render array. Which means the render array will be invalidated whenever the object's cache tag is invalidated, will cause a different version to be cached if a different translation is used (i.e. the content language cache context maps to a different language) and will expire automatically if the dependency has a max-age that isn't permanent (infinite).
See Cacheability of render arrays
— Concrete example for a full example.
Another good example is access checks, which return AccessResult
objects, which also has an AccessResult::addCacheableDependency($dependency)
method. Note that here, we only have the $dependency
parameter, because we can store the cacheability metadata of the passed in dependency on the access result object itself. (The renderer, with its render arrays, is the exception.)
Related interfaces & classes
See also
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion