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

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 () способом пойти на блочные плагины в пользовательских модулях?

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

oknate
источник
Это очень хорошее краткое изложение проблемы. Но я думаю, что вывод в 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