Як правильно налаштувати кешування для мого користувальницького блоку, що показує вміст залежно від поточного вузла?


19

У мене є цей самий базовий блок, який просто показує ідентифікатор поточного вузла.

<?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']);
  }

}

Але після кешування, блок залишається тим самим, незалежно від того, який вузол я відвідую. Як правильно кешувати результат за ідентифікатором вузла?


1
Подивіться getCacheTags()з BlockBase, вам просто потрібно додати тег, який представляє ваш вузол (вузол: {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'));
  }
}

Я перевірив це; це працює.

Просто покладіть код у файл під назвою NodeCchedBlock.php у папці модуля, змініть його область імен {module_name}, очистіть кеш і використовуйте його.


тому фокус полягає в тому, щоб видалити #cacheналаштування у функції збірки та просто додати загальнодоступні функції?
Олексій

3
Це не має значення, де ви встановлюєте теги кешу та контексти.
4к4,

Ну, я думаю, що це має більше сенсу, тому що ми будуємо блок, тому блок потрібно кешувати. Якщо ви будете змінювати свій блок у майбутньому (тобто ставити додаткові елементи візуалізації), він буде працювати.
Вагнер

@ 4k4 url.path, здається, теж працював. яка різниця?
Олексій

2
@Vagner: Розміщення тегів кешів / контекстів у масиві візуалізації також не є поганою ідеєю, оскільки ви маєте їх там, де є ваші дані, від яких залежать. І це завжди буде бурбати, тому вам не доведеться турбуватися про елементи, що знаходяться вище. Btw. ваш код чудовий, дуже добре пояснює проблеми кешування.
4к4

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: Я відредагував ваше запитання. Будь ласка, перевірте та змініть код, якщо виявили помилку.
4k4

@ 4k4 Я цього не пробував, тому що працює і інше рішення
Алекс

@Alex - Приклад повного коду: drupal.stackexchange.com/a/205155/15055
leymannx

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, блок все одно виводить 1, коли я відвідував node / 1, після очищення кешу
Alex

2
Якщо ви редагуєте включений модуль, перед його редагуванням потрібно спочатку видалити його. Очищення кеша недостатньо.
kiamlaluno

добре, але додавання 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

Дивіться цей приклад, як Drupal очікує, що масив візуалізації забезпечить необхідні метадані кешу під час оптимізації кешування за допомогою автоматичного розміщення місця та створення ледачих проблем.


Я не думаю, що це може встановити кеш об'єкта Block. '#markup' - це лише об'єкт Render Element, і немає підстав встановлювати кеш-контекст або тег. Об'єкт блоку, який потрібно відновити, коли кеш недійсний.
Вагнер

#markupможе бути кешоване так само, як і будь-який інший елемент візуалізації. У цьому випадку справа не в розмітці, а в блоці, який кешується, і ось проблема. Ви не можете вирішити це за допомогою тегів кеша, оскільки вони недійсні лише в разі зміни вузла в базі даних.
4к4,

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

краще пізно ніж ніколи :)
Алекс

0

Ви спробували реалізувати гачок_блок_взгляд_BASE_BLOCK_ID_alter?

функція гак_блок_візування_BASE_BLOCK_ID_alter (масив & $ build, \ Drupal \ Core \ Block \ BlockPluginInterface $ block) {$ build ['# cache'] ['max-age'] = 0; }

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.