Drupal 8:如何避免块缓存

我正在努力解决最近正在开发的 Drupal 8 项目中的一个问题,其中一个用于向匿名用户显示信息的块被缓存给第一个看到它的用户。这意味着访问该页面的所有后续用户都可以看到针对第一个用户的特殊消息。这仅在打开页面缓存时发生,但作为最佳实践,我不想为了解决一个小问题而关闭它。

经过一番搜索,我发现很多人都遇到了同样的问题,但问题的解决方案似乎并没有正常工作。很多人建议将#cache属性与块信息一起返回,其中包含 max-age 设置为 0。实际上我们可以提供许多值给#cache数组,这些值将影响块的缓存. 因此,在阅读这些示例后,我最终将以下数组项添加到块构建数组中。

$build['#cache']['max-age'] = 0;
$build['#cache']['contexts'] = [];
$build['#cache']['tags'] = [];

这可以通过使用UncacheableDependencyTrait以更简单的形式完成。通过将此特征添加到您的块代码中,您可以有效地执行上述所有操作,而无需向您的代码添加一个凌乱的数组。当确定块是否可以被缓存时,Drupal 将通过调用函数getCacheContexts(),getCacheTags()和向块类询问此信息getCacheMaxAge()。由于这些函数返回空值,它有效地关闭了模块的缓存。

这看起来应该有效。唯一的问题是当前版本的 Drupal(在本例中为 8.4)中似乎存在一个错误,即匿名块缓存没有传输到页面层。这意味着无论您在块中设置什么缓存标签,它都将始终受页面级缓存的约束。因此,如果您打开了匿名页面缓存,那么您的页面将强制该块缓存,而不管它具有什么缓存设置。值得庆幸的是,有一个解决方案。通过使用 KillSwitch 类,我们可以在某些情况下触发页面缓存被杀死。这个类保存在 page_cache_kill_switch 服务中,所以我们可以trigger()通过以下方式调用它的方法。

\Drupal::service('page_cache_kill_switch')->trigger();

将所有这些放在一起,我们就可以构建一个永远不会被缓存的块,并将向用户显示 IP 地址。请注意,如果您将此块添加到您网站上的每个页面,那么您的 Drupal 站点实际上将不会被缓存。我能够在我正在开发的站点上使用这个块,因为它只会出现在站点的单个页面上。

<?php
 
namespace Drupal\ip_address\Plugin\Block;
 
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\UncacheableDependencyTrait;
 
/**
 * Provides an IP Address Block.
 *
 * @Block(
 *   id = "ip_address_block",
 *   admin_label = @Translation("IP Address"),
 *   category = @Translation("IP Address"),
 * )
 */
class IpAddressBlock extends BlockBase {
 
  use UncacheableDependencyTrait;
 
  /**
   * {@inheritdoc}
   */
  public function build() {
    // 初始化块数据。
    $build = [];
 
    // 不要缓存带有此块的页面。
    \Drupal::service('page_cache_kill_switch')->trigger();
    // 获取用户IP地址。
    $userIp = \Drupal::request()->getClientIp();
 
    if ($userIp == '192.168.88.11') {
      $build['content'] = [
        '#markup' => $this->t("Your IP address @address matches.", ['@address' => $userIp]),
      ];
    }
 
    return $build;
  }
}

另一个很好的例子(也许是一种更简单的测试方法)是通过检测用户用来访问站点的浏览器。

<?php
 
namespace Drupal\ip_address\Plugin\Block;
 
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\UncacheableDependencyTrait;
 
/**
 * Provides an IP Address Block.
 *
 * @Block(
 *   id = "ip_address_block",
 *   admin_label = @Translation("IP Address"),
 *   category = @Translation("IP Address"),
 * )
 */
class IpAddressBlock extends BlockBase {
 
  use UncacheableDependencyTrait;
 
  /**
   * {@inheritdoc}
   */
  public function build() {
    // 初始化块数据。
    $build = [];
 
    // 不要缓存带有此块的页面。
    \Drupal::service('page_cache_kill_switch')->trigger();
 
    if (stristr($_SERVER['HTTP_USER_AGENT'], 'firefox')) {
      $build['content'] = [
        '#markup' => $this->t("User agent @browser found.", ['@browser' => $_SERVER['HTTP_USER_AGENT']]),
      ];
    }
    return $build;
  }
}