如何根据当前节点为显示内容的自定义块正确设置缓存?


19

我有一个非常基本的块,它仅显示当前节点的ID。

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\ExampleEmptyBlock.
 */

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * @Block(
 *   id = "example_empty",
 *   admin_label = @Translation("Example: empty block")
 * )
 */
class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $node = \Drupal::routeMatch()->getParameter('node');
    $build = array();

    if ($node) {
      $config = \Drupal::config('system.site');

      $build = array(
        '#type' => 'markup',
        '#markup' => '<p>' . $node->id() . '<p>',
        '#cache' => array(
          'tags' => $this->getCacheTags(),
          'contexts' => $this->getCacheContexts(),
        ),
      );
    }

    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

}

但是,一旦缓存,该块将保持不变,无论我访问哪个节点。如何正确缓存每个节点ID的结果?


1
getCacheTags()从BlockBase 看,您只需要添加一个代表您的节点(节点:{nid})的标签即可。抱歉,我现在很着急,我可以稍后再解释,
-Vagner

Answers:


31

这是带有注释的完整工作代码。

namespace Drupal\module_name\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;

/**
 * Provides a Node cached block that display node's ID.
 *
 * @Block(
 *   id = "node_cached_block",
 *   admin_label = @Translation("Node Cached")
 * )
 */
class NodeCachedBlock extends BlockBase {
  public function build() {
    $build = array();
    //if node is found from routeMatch create a markup with node ID's.
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      $build['node_id'] = array(
        '#markup' => '<p>' . $node->id() . '<p>',
      );
    }
    return $build;
  }

  public function getCacheTags() {
    //With this when your node change your block will rebuild
    if ($node = \Drupal::routeMatch()->getParameter('node')) {
      //if there is node add its cachetag
      return Cache::mergeTags(parent::getCacheTags(), array('node:' . $node->id()));
    } else {
      //Return default tags instead.
      return parent::getCacheTags();
    }
  }

  public function getCacheContexts() {
    //if you depends on \Drupal::routeMatch()
    //you must set context of this block with 'route' context tag.
    //Every new route this block will rebuild
    return Cache::mergeContexts(parent::getCacheContexts(), array('route'));
  }
}

我测试过了 有用。

只需将代码放入模块文件夹中名为NodeCachedBlock.php的文件中,更改其命名空间{module_name},清除缓存并使用它。


所以诀窍是删除#cache构建功能中的设置,而仅添加公共功能?
亚历克斯

3
设置缓存标记和上下文的位置无关紧要。
4k4

好吧,我认为这更有意义,因为我们正在构建一个块,因此需要缓存块。如果您将来更改块(即添加一些额外的渲染元素),则块将起作用。
Vagner

@ 4k4 url​​.path似乎也起作用。有什么不同?
亚历克斯

2
@Vagner:将缓存标签/上下文放在渲染数组中也不是一个坏主意,因为您可以将缓存标签/上下文放在数据所在的位置,这取决于数据。而且它总是会冒泡,因此您不必担心上面的元素。顺便说一句。您的代码很棒,确实很好地解释了缓存问题。
4k4

13

到目前为止,最简单的方法是依靠插件/块上下文系统。

请参阅我的答案,以了解如何制作一个提取当前节点内容的块?

您只需要在块注释中放置一个节点上下文定义,如下所示:

*   context = {
*     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
*   }

然后像这样使用它: $this->getContextValue('node')

这样做的好处是Drupal会为您处理缓存。自动地。因为它知道默认的节点上下文(并且仅就核心而言)是当前节点。而且知道了它的来源,因此自动添加了缓存上下文和缓存标签。

通过\Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()和相应的getCacheTags()方法,BlockBase /您的块类从该类扩展而来,并继承这些方法。


您替换为\Drupal::routeMatch()->getParameter('node')$this->getContextValue('node')并用一行代码解决了整个缓存问题?大!
4k4

1
到目前为止,谢谢!您能否提供完整的代码示例?
Alex

@Alex:我编辑了你的问题。如果发现任何错误,请检查并更改代码。
4k4,21

@ 4k4我没有尝试过,因为其他解决方案也可行
Alex


7

如果从派生块插件的类Drupal\Core\Block\BlockBase,则将有两种方法来设置缓存标记和上下文。

  • getCacheTags()
  • getCacheContexts()

例如,Book模块块实现了以下方法。

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['route.book_navigation']);
  }

论坛模块块使用以下代码。

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    return Cache::mergeTags(parent::getCacheTags(), ['node_list']);
  }

在您的情况下,我将使用以下代码。

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    $node = \Drupal::routeMatch()->getParameter('node');
    return Cache::mergeTags(parent::getCacheTags(), ["node:{$node->id()}"]);
  }

您也可以使用以下方法,使该块完全不可缓存(即使我会避免)。也许在其他情况下可能有用。

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return 0;
  }

use Drupal\Core\Cache\Cache;如果要使用Cache该类,请记住在文件顶部添加。


谢谢,但是在/ node / 2上,当我清除缓存后,当我第一次访问node / 1时,该块仍然输出1
Alex

2
如果要编辑已启用的模块,则需要先将其卸载,然后再进行编辑。清除缓存是不够的。
kiamlaluno

好的,但是添加maxAge 0可以正常工作!
亚历克斯(Alex)

另外,您的块类是否将该BlockBase类用作父类?
kiamlaluno

是的,它确实使用了它
Alex

3

构建渲染数组时,请始终附加正确的元数据:

use Drupal\Core\Cache\Cache;

$build['node_id'] = [
  '#markup' => '<p>' . $node->id() . '<p>',
  '#cache' => [
    'tags' => $node->getCacheTags(),
    // add a context if the node is dependent on the route match
    'contexts' => ['route'],
  ],
];

这不是特定于块的,并且块插件缓存依赖方法getCacheTags(),getCacheContext()和getCacheMaxAge()不能替代。它们仅应用于其他缓存元数据,而这些元数据不能通过渲染数组传递。

请参阅文档:

“将渲染数组的可缓存性告知Render API,这一点至关重要。”

https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays

看到这个例子中的Drupal如何期望优化通过自动placeholdering和懒建设缓存时渲染阵列以提供必要的高速缓存元数据与用户上下文定制块上设置问题的用户专用高速缓存标签


我不认为可以设置Block对象的缓存。'#markup'只是一个渲染元素对象,没有理由设置缓存上下文或标记。缓存无效时需要重建的块对象。
瓦格纳

#markup可以与任何其他render元素一样进行缓存。在这种情况下,不是标记,而是缓存的块,这就是问题所在。您无法使用缓存标记来解决它,因为如果在数据库中更改了节点,它们只会变得无效。
4k4

@Vagner您可以设置一个Block对象的缓存。该BlockBase班有甚至必要的方法。
kiamlaluno

1
对我来说return [ '#markup' => render($output), '#cache' => [ 'contexts' => ['url'] ] ];,每个URL缓存的工作都非常好。
leymannx

1
是的,@ leymannx,就这么简单。此线程似乎过分考虑了这个问题。
4k4

0

这里的问题是,缓存上下文未在构建函数中的正确位置声明:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');
   $build = array();

   if ($node) {
    $config = \Drupal::config('system.site');

    $build = array(
    '#type' => 'markup',
    '#markup' => '<p>' . $node->id() . '<p>',
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );
 }
 return $build;
 }
}

如果在非节点上调用该块,则构建函数将返回一个空数组,因此该块没有缓存上下文,并且该行为将被drupal缓存:该块的显示不会正确无效或呈现。

解决方案是每次都使用缓存上下文初始化$ build:

class ExampleEmptyBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
   $node = \Drupal::routeMatch()->getParameter('node');

    $build = array(
    '#cache' => array(
      'tags' => $this->getCacheTags(),
      'contexts' => $this->getCacheContexts(),
    ),
   );

   if ($node) {
    $config = \Drupal::config('system.site');

    $build['#markup'] = '<p>' . $node->id() . '<p>';
    $build['#type'] = 'markup';
    }
 return $build;
 }
}

0

我意识到自己这次对话很晚,但是下面的代码对我有用:

class ExampleBlock extends BlockBase
{

  public function build()
  {
    $lcContent = '';

    $loNode = \Drupal::routeMatch()->getParameter('node');

    if (!$loNode)
    {
      return (array(
        '#type' => 'markup',
        '#cache' => array('max-age' => 0),
        '#markup' => $lcContent,
      ));
    }

    $lcContent .= "<div id='example_block' style='overflow: hidden; clear: both;'>\n";
    $lcContent .= $loNode->id();
    $lcContent .= "</div>\n";

    return (array(
      '#type' => 'markup',
      '#cache' => array('max-age' => 0),
      '#markup' => $lcContent,
    ));
  }
}

迟到总比没有好:)
Alex

0

您是否尝试过实现hook_block_view_BASE_BLOCK_ID_alter?

函数hook_block_view_BASE_BLOCK_ID_alter(array&$ build,\ Drupal \ Core \ Block \ BlockPluginInterface $ block){$ build ['#cache'] ['max-age'] = 0; }

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.