web-dev-qa-db-ja.com

現在のノードに応じてコンテンツを表示するカスタムブロックのキャッシュを正しく設定するにはどうすればよいですか?

現在のノードの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ごとに結果を正しくキャッシュするにはどうすればよいですか?

22
Alex

これはコメント付きの完全に機能するコードです。

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}を変更して、キャッシュをクリアして使用します。

36
Vagner

これを行う最も簡単な方法は、プラグイン/ブロックコンテキストシステムに依存することです。

私の答えを参照してください 現在のノードのコンテンツをプルするブロックを作成するにはどうすればよいですか

次のように、ブロックアノテーションにノードコンテキスト定義を配置するだけです。

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

そして、それを次のように使用します:$this->getContextValue('node')

これのいい点は、Drupalが自動的にキャッシングを処理することです。自動的に。デフォルトの(そしてコアのみ)ノードコンテキストが現在のノードであることがわかっているためです。そして、それはどこから来ているかを知っているので、キャッシュコンテキストとキャッシュタグが自動的に追加されます。

\Drupal\Core\Plugin\ContextAwarePluginBase::getCacheContexts()および対応するgetCacheTags()メソッドを介して、BlockBase /ブロッククラスはそれから拡張され、それらのメソッドを継承します。

14
Berdir

_Drupal\Core\Block\BlockBase_ からブロックプラグインのクラスを派生させる場合、キャッシュタグとコンテキストを設定する2つの方法があります。

  • 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;
  }
_

Cacheクラスを使用する場合は、ファイルの先頭に_use Drupal\Core\Cache\Cache;_を追加してください。

8
kiamlaluno

レンダー配列を作成するときは、常に正しいメタデータを添付してください。

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()は代わりのものではありません。これらは、追加のキャッシュメタデータにのみ使用する必要があります。これは、レンダー配列を介して配信することはできません。

ドキュメントを参照してください:

「レンダー配列のキャッシュ可能性をレンダーAPIに通知することが最も重要です。」

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

この例を参照してくださいDrupalは、自動プレースホルダーとレイジービルドを介してキャッシュを最適化するときに、レンダー配列が必要なキャッシュメタデータを提供することを期待します ユーザーのカスタムブロックでユーザー固有のキャッシュタグを設定する問題コンテキスト

5
4k4

hook_block_view_BASE_BLOCK_ID_alter

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

drupal 8でブロックキャッシングを理解するのにかなりの時間を要したので、私は ブロックキャッシングを説明するこのビデオチュートリアル(16分) を作成しました。

1

ここでの問題は、キャッシュコンテキストがビルド関数の適切な場所で宣言されていないことです。

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;
 }
}
1
Guile

私はこの会話に遅れていることを認識していますが、以下のコードがうまくいきました:

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,
    ));
  }
}
1
Eddie Fann