返回博客

Drupal 预处理钩子:为什么 `url.path` 缓存上下文很重要

2025-09-068 分钟阅读

当您在预处理钩子中根据当前路由或路径修改输出时,您还需要告知 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.pathroute
  • 当前用户 → 添加 user(或更细粒度的上下文,如 user.roles
  • 语言 → 添加 languages:language_interface
  • 主题或断点 → 添加 themeresponsive_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.pathroute
  • 它是否依赖于实体?为该实体添加缓存标签
  • 它是否因用户或语言而异?添加匹配的上下文
  • 保持 max-age 较高;依靠标签进行失效。

要点

是的——如果您的预处理逻辑会因页面而异地更改输出,您就必须声明相应的缓存上下文。否则,Drupal 将乐于在所有地方提供相同的缓存结果。