Как правильно установить контексты кэша для пользовательских блоков?


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

Это также исправило проблему, но, что интересно, когда я выводил переменные в функции блока предварительной обработки, они не отображаются в $ variable ['# cache'] ['contexts'], но они отображаются в $ variable ['elements '] [' # кэшировать '] [' контексты]

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

Я пытаюсь выяснить, как это работает, и почему это не работает из функции сборки.

Глядя на /core/modules/block/src/BlockViewBuilder.php функцию viewMultiple (), похоже, что она извлекает теги кеша из сущности и плагина:

'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.
— Бердир

1
Отключение динамического кэша страниц не решает проблему.
— oknate

1
Хм, это может быть проблема с тем фактом, что ваш элемент верхнего уровня не содержит ничего, кроме #cache. Вы пытались просто установить #cache в вашей форме? Это форма, которая должна меняться в зависимости от них, и поскольку кеш-теги всплывают, это должно сработать. И если вы когда-либо используете свою форму в другом блоке или другом месте, она также должна просто работать там.
— Бердир

1
Я быстро взломал SyndicateBlock и использовал этот метод build (): gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Помещение, что на моем сайте работает просто отлично, контексты кеша видны в таблице cache_render и отображается правильный URI запроса. Вы можете попробовать то же самое? Мне кажется, что на вашем сайте происходит что-то очень странное
— Бердир

2
Используете ли вы стандартный шаблон блока или пользовательский? См. Drupal.stackexchange.com/questions/217884/…
— 4k4

Ответы:


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) Следует ли создать собственный шаблон темы, чтобы это исправить, или просто добавить метод для getCacheContexts () в свой пользовательский плагин Block? Лучше создать новый шаблон темы, а не добавлять метод getCacheContexts (), который переопределяет естественный порядок всплытия контекстов кэша и может более глубоко нарушать метаданные в массиве сборки.


Это очень хорошее краткое изложение проблемы. Но я думаю, что вывод в 3) проблематичен, потому что вы не только разбиваете, что ваши собственные метаданные кэша могут пузыриться, но также и то, что может быть глубже внутри массива рендеринга, и вы можете не знать об этом.
— 4k4

Итак, вы бы предложили создать новый шаблон темы? Чтобы сохранить прозрачное пузырение?
— oknate

Да, используйте шаблон блока только для добавления внешних элементов. Постройте внутреннюю часть блока в build (). Для этого используйте пользовательские шаблоны или элементы рендеринга, например таблицу или базовый шаблон, например ссылки.
— 4k4

Хорошо, я обновлю ответ.
— oknate

Тьфу, я не понимал, что детализация Твиг будет игнорировать кэшируемые метаданные. Это может означать, что в конце мы должны использовать наш собственный пользовательский метод для детализации (что делает расширение ветки бесполезным), чтобы мы сохраняли метаданные при переходе на более низкие уровни. Хорошая находка!
— 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.