在自定义块上设置缓存上下文的正确方法是什么?


13

我遇到了一个问题,即对于注销用户而言,每页应该唯一的一个块不适用。问题是我在视图搜索页面上拥有一个自定义块插件,该插件包含自定义过滤器(有点像自定义替换暴露的过滤器。通过/ admin / structure / block放置的块)。

根据我对Drupal 8的了解,我将缓存上下文添加到构建数组中:

  public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      'search_form' => $search_form,
      '#cache' => ['contexts' => ['url.path', 'url.query_args']]
    ];

  }

但是看来这一定是不正确的,因为注销后,该块将在第一个视图上缓存,并且当url更改时,它没有显示该块的新版本。

我认为可能是导致问题的视图页面,但是即使我在视图页面上关闭了缓存,问题仍然存在。

我可以通过几种方式解决此问题,例如,使用preprocess_block挂钩:

function mymodule_preprocess_block__mycustomsearchblock(&$variables) {
  $variables['#cache']['contexts'][] = 'url.path';
  $variables['#cache']['contexts'][] = 'url.query_args';
}

但这让我感到困扰,我不能只是将缓存上下文放入块的构建数组中。

由于我的代码块扩展了BlockBase,因此我决定尝试使用getCacheContexts()方法,尤其是因为我看到核心中的某些模块正在这样做。

  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['url.path', 'url.query_args']);
  }

这也解决了这个问题,但是有趣的是,当我在预处理块函数中输出变量时,这些变量未显示在$ variables ['#cache'] ['contexts']中,但它们的确显示在$ variables ['elements中'] ['#cache'] ['contexts']

array:5 [▼
  0 => "languages:language_interface"
  1 => "theme"
  2 => "url.path"
  3 => "url.query_args"
  4 => "user.permissions"
]

我试图通过构建函数弄清楚它是如何工作的以及为什么它不起作用。

在viewMultiple()函数中查看/core/modules/block/src/BlockViewBuilder.php,看起来它从实体和插件中提取了缓存标签:

'contexts' => Cache::mergeContexts(
  $entity->getCacheContexts(),
  $plugin->getCacheContexts()
),

因此,这说明了为什么将getCacheContexts()方法添加到我的块插件中,为什么会将上下文添加到我的块中。另外,在同一类中查看preRender方法,看起来它没有在块构建功能中使用缓存数组,这使我感到困惑,因为在Drupal 8中添加缓存的方法似乎是添加#cache元素来渲染元素。

所以我的问题是

1)是否将缓存上下文直接添加到块插件中的数组上而忽略了?

2)如果是这样,是否有办法解决,我们是否需要将其添加到构建数组的子元素中?

3)如果直接添加的上下文被忽略,是否在自定义模块中添加getCacheContexts()成为块插件的方法?


1
1)不,您的阻止内容实际上是一个较低级别的内容,应在以后合并。2)不需要,因为1、3)实现getCacheContexts()可以更容易/更清洁,但不是必需的。您明确提到匿名用户,您确定它也不会影响正常身份验证的用户吗?如果禁用dynamic_page_cache,问题是否会消失?如果仅影响匿名用户,则必定会发生一些奇怪的事情,因为内部页面缓存始终会随url / query args的不同而变化。
贝尔迪尔

1
禁用动态页面缓存不能解决问题。
oknate,2016年

1
嗯,您的顶级元素除了#cache之外不包含其他任何内容,可能是一个问题。您是否尝试过简单地在表单中设置#cache?这种形式需要随这些而变化,并且由于缓存标签冒泡,因此应该可以使用。而且,如果您曾经在其他区域或其他地方使用表单,那么它也应该在那里工作。
贝尔迪尔

1
我迅速入侵了SyndicateBlock,并使用了以下build()方法:gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c。将其放置在我的网站上工作正常,在cache_render表中可见缓存上下文,并显示正确的请求URI。你可以试试看吗?在我看来,您网站上正在发生非常奇怪的事情
Berdir 2016年

2
您使用标准块模板还是自定义块模板?见drupal.stackexchange.com/questions/217884/...
4k4

Answers:


9

在大多数情况下,您只需在build()方法中返回的渲染数组上直接设置缓存上下文。

我终于在@Berdir和@ 4k4的帮助下找到了我的问题所在。如果您使用的是自定义模板,例如block--myblock.html.twig,并且分别输出变量,例如{{content.foo}},而不是像{{content}}一样同时输出所有变量,则它将忽略注销后,您的缓存上下文将直接传递到块构建数组中。请参见在自定义块上设置缓存上下文的正确方法是什么?

因此,要回答原始问题:

1)有时会忽略直接传递到自定义块插件中的缓存上下文。您可以通过更改SyndicateBlock进行测试,然后在主题块中创建一个自定义模板(syndicate.html.php),在其中以如下方式分别输出变量:

{% block content %}
  {{ content.foo }}
{% endblock %}

更改url参数时,您会看到该块不尊重缓存上下文。

现在,如果您对其进行更改,则将所有内容作为一个片段输出,它将起作用:

{% block content %}
  {{ content }}
{% endblock %}

现在,它尊重缓存上下文,并且每个页面的块都是唯一的。

2)现在,要解决此问题,您只需在自己的模板中输出块中的内容即可。

 public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      '#theme' => 'mycustomtemplate',
      '#search_form' => $search_form,
      '#cache' => ['contexts' => ['url.path', 'url.query_args']]
    ];

  }

这避免了模块模块深奥的缓存异常,并且注销后,您的表单现在每页都是唯一的。

3)您应该创建自己的主题模板来解决此问题,还是在自定义Block插件中为getCacheContexts()添加方法?最好创建一个新的主题模板,而不是添加一个覆盖缓存上下文冒泡的自然顺序的getCacheContexts()方法,并可能在构建数组中更深层地破坏元数据。


这是问题的很好总结。但是我认为3)中的结论是有问题的,因为您不仅打破了自己的缓存元数据可能冒泡的现象,而且还打破了渲染数组内部更深层的知识,而您可能没有意识到。
2016年

因此,您建议创建一个新的主题模板?要保留清晰的气泡?
oknate

是的,仅使用块模板将事物添加到外部。在build()中构建块的内部。为此,请使用自定义模板,也可以使用渲染元素(如表格)或核心模板(如链接)。
2016年

好,我将更新答案。
oknate

gh,我没有意识到Twig的深入研究会忽略可缓存的元数据。这可能意味着我们最终需要使用自己的自定义方法进行钻取(这使树枝扩展名无用),以便在保留元数据的同时将其下移。好发现!
LionsAd

4

对于任何发现这个的人...

渲染content(或content|without())起作用的原因是,渲染数组content['#cache']中存在一个元素,其中包含该内容的所有可缓存元数据。

如果您不允许将此内容呈现在树枝中,请使用content{{'#cache': content['#cache']|render }},该页面不知道其具有可缓存的元数据(例如,它永远不会冒泡)。

听起来您好像没有在做自定义树枝。如果您使用的是Varnish之类的东西,那么这也可能是匿名用户的罪魁祸首。


3

我也遇到了这个问题,我想出的解决方法是根据主要内容变量的过滤版本创建一个新的block_content变量,但不包括我要手动呈现的任何自定义字段:

{% set block_content = content|without('field_mycustomfield', 'field_mycustomfield2') %}

然后,而不是稍后直接渲染“ content.body”变量,而是调用:

{{ block_content }}

如果要分别渲染每个字段,则可以将它们继续添加到“无”过滤器中,以便渲染block_content除了修复缓存之外不执行任何操作。


0

实现此目的的更简单方法是声明并定义getCacheContexts()方法


  public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      'search_form' => $search_form
    ];

  }

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    // If you need to redefine the Max Age for that block
    return 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return ['url.path', 'url.query_args'];
  }

查看CacheableDependency文档,其中应包含您需要的所有内容;)


这不工作了块现在的呈现方式,请参阅drupal.stackexchange.com/questions/288881/...
4k4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.