CacheableDependencyInterface & friends

Last updated on
13 October 2016

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