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


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から見て、ノード(node:{nid})を表すタグを追加するだけです。申し訳ありませんが、私は今、急いでよ、私は、後でよりよく説明することができます
バグネル・

回答:


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

まあ、私たちはブロックを構築しているので、それはより理にかなっていると思うので、ブロックをキャッシュする必要があります。将来、ブロックを変更する(つまり、追加のレンダリング要素を追加する)場合、ブロックは機能します。
ヴァグナー

@ 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')、1行のコードでキャッシュの問題全体を解決しますか?すごい!
4k4

1
これまでありがとう!完全なコード例を提供できますか?
アレックス

@アレックス:あなたの質問を編集しました。エラーを見つけた場合は、コードを確認して変更してください。
4k4

@ 4k4他のソリューションも動作するため、試していない
アレックス

@Alex -完全なコード例:drupal.stackexchange.com/a/205155/15055
leymannx

7

ブロックプラグインのクラスをから派生させる場合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;
  }

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


感謝しますが、/ node / 2では、キャッシュをクリアした後、最初にnode / 1にアクセスしたときにブロックは1を出力します。
アレックス

2
有効なモジュールを編集している場合は、編集する前にまずアンインストールする必要があります。キャッシュをクリアするだけでは不十分です。
キアムルノ

わかりましたが、奇妙なことにmaxAge 0を追加しても機能します!
アレックス

また、ブロッククラスはBlockBaseクラスを親クラスとして使用しますか?
kiamlaluno

はい、それを使用します
アレックス

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

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

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

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

自動placeholderingとレイジー構築を通じてキャッシュを最適化する際にDrupalは必要に応じてキャッシュメタデータを提供するために、レンダリングの配列を期待どのようにこの例を参照してくださいユーザーコンテキストでカスタムブロックにユーザー固有のキャッシュタグの設定の問題


Blockオブジェクトのキャッシュを設定できるとは思いません。「#markup」は単なるRender Elementオブジェクトであり、キャッシュコンテキストまたはタグを設定する理由はありません。キャッシュが無効になったときに再構築する必要があるブロックオブジェクト。
ヴァグナー

#markup他のレンダリング要素と同じようにキャッシュできます。この場合、それはマークアップではなく、キャッシュされているブロックであり、ここに問題があります。データベースでノードが変更された場合にのみ無効になるため、キャッシュタグで解決することはできません。
4k4

@Vagner Blockオブジェクトのキャッシュを設定できます。BlockBaseクラスでも、必要なメソッドを持っています。
キアムルノ

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

遅くないより良い:)
アレックスアレックス

0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.