logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

CacheableDependencyInterface

03/10/2025, by Ivan

为了简化与缓存元数据(缓存标签缓存上下文最大寿命 max-age)的工作,Drupal 8 提供了 CacheableDependencyInterface。

为什么?

想象一下,如果你必须在渲染数组(或其他一些计算)中手动为你使用的每个实体对象和配置对象创建缓存标签。在多语言网站中,你还必须手动添加所需的缓存上下文(无论是翻译后的实体,还是带有语言覆盖的配置对象)。

不仅是实体和配置,还包括 访问结果区块插件菜单链接上下文插件条件插件等,因为它们最终都会导致渲染(某种类型的计算),而渲染是我们希望缓存的。

在 Drupal 8 开发的早期确实如此。显然,这样做不可靠,而且非常容易出错。

这就是为什么引入了 CacheableDependencyInterface。顾名思义:实现该接口的对象可以自动成为缓存依赖。

例如,在创建一个渲染数组 <p>Hi, %user, welcome to %site!</p> 时,你依赖于当前用户的 User 实体和 system.site 配置。当这个渲染数组被缓存时,它会将该用户实体和该配置对象作为它的缓存依赖。

CacheableDependencyInterface 可以由任何值对象实现(即表示逻辑数据单元的对象)。如果你查看它的 API 文档,你会发现 Drupal 8 核心中大量重要对象都实现了它。事实上,可以肯定地说,大多数你在编写 Drupal 8 代码时会使用的对象都实现了它!

有两个相反的极端情况,Drupal 为此提供了方便的 Trait:不可变对象(因此永久可缓存,使用 UnchangingCacheableDependencyTrait,它始终返回 max-age === permanent);以及总是动态计算、因此永远不缓存的对象(使用 UncacheableDependencyTrait,它始终返回 max-age === 0)。

RefinableCacheableDependencyInterface

但是 CacheableDependencyInterface 只能处理对象固有的、规范的缓存元数据。有时一个对象可能存在多个变体。

最典型的例子是实体翻译(相同的实体 ID,但不同的翻译版本)和配置翻译(相同的配置名,但带有语言覆盖)。

在这两种情况下,原始对象(未翻译的)已有的缓存元数据依然适用(例如 node:5 缓存标签)。但是 —— 在实体的情况下,需要内容语言的缓存上下文('languages:'. LanguageInterface::TYPE_CONTENT,见 缓存上下文),以表明该实体是原始实体的一个依赖语言的变体。同样,对于配置对象,需要界面语言缓存上下文('languages:'. LanguageInterface::TYPE_INTERFACE),以表明该配置对象是原始对象的一个依赖界面语言的变体。

另一个超出翻译的例子是“海盗日”模块,它的配置覆盖仅在海盗日生效,向配置中添加 yar、har 和随机鹦鹉;那么配置对象将具有 pirate_day 缓存上下文。

在上述所有例子中,我们需要细化对象的缓存元数据,以指明具体的变体。为此,引入了 RefinableCacheableDependencyInterface,它允许这样做:添加缓存标签、上下文并更新最大寿命。

为了简化该接口的实现,还有一个方便的 Trait:RefinableCacheableDependencyTrait

关于实体和配置对象

Drupal 8 中的所有实体(核心、贡献模块和自定义模块)都实现了 EntityInterface,该接口扩展了 CacheableDependencyInterfaceRefinableCacheableDependencyInterface。此外,核心中的所有实体都扩展了 抽象基类 Entity,contrib/custom 模块也推荐这样做。这意味着你在 Drupal 8 中使用的每个实体,都会自动带有一致的缓存标签(<实体类型>:<实体 ID>,例如 node:5、user:3)和缓存上下文来反映翻译。

Drupal 8 核心中的所有配置对象都扩展了 抽象基类 ConfigBase,该类实现了 CacheableDependencyInterface 和 RefinableCacheableDependencyInterface。这意味着你在 Drupal 8 中使用的每个配置对象,都会自动带有一致的缓存标签(形式为 config:<配置名>,例如 config:system.performance),以及用于反映配置覆盖的缓存上下文(翻译是核心中唯一的示例)。

最后,Drupal 8 中的所有实体和配置对象都会自动拥有内容语言/界面语言的缓存上下文,这要归功于 EntityManager::getTranslationFromContext()LanguageConfigFactoryOverride::getCacheableMetadata($name)

使用作为缓存依赖的对象

渲染是最常见的依赖缓存对象的例子。为简化操作,我们有 RendererInterface::addCacheableDependency($build, $dependency) —— 其中 $build 是依赖于 $dependency 对象的渲染数组;缓存对象的元数据会自动“传递”到渲染数组。这意味着当依赖对象的缓存标签失效时,渲染数组也会失效并重新生成;当切换到另一种语言时(即内容语言缓存上下文不同),也会生成不同的版本;如果依赖有非永久(有限)的 max-age,缓存也会在到期时自动失效。

参见渲染数组可缓存性——完整示例。

另一个很好的例子是访问检查,返回 AccessResult 对象。它们也有一个方法 AccessResult::addCacheableDependency($dependency)。注意,这里只有 $dependency 参数,因为访问结果对象本身可以存储缓存依赖的元数据。(渲染是个例外,因为它使用渲染数组。)

相关接口和类

另请参阅