Drupal 预处理钩子:为什么 `url.path` 缓存上下文很重要
2025-09-06•8 分钟阅读
当您在预处理钩子中根据当前路由或路径修改输出时,您还需要告知 Drupal 的渲染缓存哪些输出是可变的。否则,第一个渲染结果可能会被缓存并在所有地方重复使用。
情况
考虑一个自定义的品牌区块预处理,它在博客节点上显示不同的站点名称:
getRouteName();
// 检查我们是否在博客页面上
if ($route_name === 'entity.node.canonical') {
$node = $route_match->getParameter('node');
if ($node && $node->bundle() === 'blog') {
// 在博客页面上,修改站点名称以显示 "HELLO WORLD"
$variables['site_name'] = 'HELLO WORLD';
}
}
// 在主页和其他页面上,保留原始站点名称
}
如果没有添加缓存上下文的行($variables['#cache']['contexts'][] = 'url.path';
),Drupal 可能会缓存它渲染的第一个版本(例如博客版本),并在每个页面上重复使用。这就是为什么您在所有地方都看到相同文本的原因。
为什么会发生这种情况
- 渲染缓存:Drupal 为了性能会缓存渲染数组。
- 可变输出:如果输出因路径或路由而异,缓存键必须包含这种可变性。
- 缓存上下文:添加
url.path
(或更具体的上下文)会告诉 Drupal 为每个路径维护单独的缓存变体。
这是否适用于所有预处理钩子?
是的,该规则适用于任何可变的可渲染输出。预处理钩子通常会修改渲染数组或模板变量,这些变量会成为渲染缓存的一部分。如果您的逻辑因以下因素而异:
- 路径或路由 → 添加
url.path
或route
- 当前用户 → 添加
user
(或更细粒度的上下文,如user.roles
) - 语言 → 添加
languages:language_interface
- 主题或断点 → 添加
theme
、responsive_image_style
等。
预处理本身并不特殊;重要的是最终的渲染数组是否应该在请求之间以不同的方式缓存。如果输出可变,请声明正确的缓存上下文。
选择正确的缓存上下文
- 优先精确性:如果您依赖于规范节点,请考虑使用
route
而不是url.path
,以避免不必要的缓存碎片(例如,查询字符串或别名)。 - 实体驱动的区块:当您依赖于实体(如节点)时,请添加缓存依赖项,以便编辑能够正确清除:
$variables['#cache']['tags'][] = 'node:' . $node->id();
- Max-age:将
max-age
保留为默认值(永久),除非输出确实会过期。
更安全的替代方案:从渲染数组派生
除了使用全局变量,您还可以使用 CacheableMetadata
附加可缓存元数据,以安全地合并上下文和标签:
addCacheContexts(['route']);
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() === 'entity.node.canonical') {
$node = $route_match->getParameter('node');
if ($node && $node->bundle() === 'blog') {
$variables['site_name'] = 'HELLO WORLD';
$cacheable->addCacheTags(['node:' . $node->id()]);
}
}
$cacheable->applyTo($variables);
}
清单
- 输出是否因路径或路由而改变?添加
url.path
或route
。 - 它是否依赖于实体?为该实体添加缓存标签。
- 它是否因用户或语言而异?添加匹配的上下文。
- 保持
max-age
较高;依靠标签进行失效。
要点
是的——如果您的预处理逻辑会因页面而异地更改输出,您就必须声明相应的缓存上下文。否则,Drupal 将乐于在所有地方提供相同的缓存结果。