web-dev-qa-db-fra.com

Quelle est la bonne façon de définir des contextes de cache sur des blocs personnalisés?

J'ai rencontré un problème où un bloc qui devrait être unique par page n'est pas destiné aux utilisateurs déconnectés. Le problème est un plugin de bloc personnalisé que j'ai sur une page de recherche de vues qui contient des filtres personnalisés (un peu comme un remplacement personnalisé pour les filtres exposés. Le bloc placé via/admin/structure/block).

Sur la base de ce que j'ai appris sur Drupal 8, j'ai ajouté les contextes de cache à mon tableau de build:

  public function build() {

    $search_form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\SearchForm');
    return [
      'search_form' => $search_form,
      '#cache' => ['contexts' => ['url.path', 'url.query_args']]
    ];

  }

Mais il semble que cela doit être incorrect, car une fois déconnecté, le bloc serait mis en cache sur la première vue, et lorsque l'URL a changé, il n'affichait pas une nouvelle version du bloc.

Je pensais que c'était peut-être la page d'affichage qui causait le problème, mais même lorsque j'ai désactivé la mise en cache sur la page d'affichage, le problème est resté.

J'ai pu résoudre le problème de plusieurs manières, par exemple, en utilisant un hook preprocess_block:

function mymodule_preprocess_block__mycustomsearchblock(&$variables) {
  $variables['#cache']['contexts'][] = 'url.path';
  $variables['#cache']['contexts'][] = 'url.query_args';
}

Mais cela me dérangeait de ne pas pouvoir simplement mettre les contextes de cache dans le tableau de construction de mon bloc.

Étant donné que mon bloc étend BlockBase, j'ai décidé d'essayer la méthode getCacheContexts (), d'autant plus que j'ai vu certains modules dans le noyau le faire de cette façon.

  public function getCacheContexts() {
    return Cache::mergeContexts(parent::getCacheContexts(), ['url.path', 'url.query_args']);
  }

Le problème est également résolu, mais il est intéressant de noter que lorsque je génère les variables dans la fonction de bloc de prétraitement, celles-ci ne s'affichent pas dans $ variables ['# cache'] ['contextes'], mais elles apparaissent dans les éléments $ variables [' '] [' # cache '] [' contextes ']

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

J'essaie de comprendre comment cela fonctionne et pourquoi cela ne fonctionnait pas à partir de la fonction de génération.

En regardant /core/modules/block/src/BlockViewBuilder.php à la fonction viewMultiple (), il semble qu'il tire les balises de cache de l'entité et du plugin:

'contexts' => Cache::mergeContexts(
  $entity->getCacheContexts(),
  $plugin->getCacheContexts()
),

Cela explique donc pourquoi l'ajout d'une méthode getCacheContexts () à mon plugin de bloc ajoute les contextes à mon bloc. De plus, en regardant la méthode preRender dans la même classe, il semble qu'elle n'utilise pas le tableau de cache dans la fonction de construction de blocs, ce qui me confond, car il semble que le moyen d'ajouter la mise en cache dans Drupal 8 est de ajoutez un élément #cache pour rendre les éléments.

Donc ma question est,

1) Les contextes de cache ajoutés directement sur la baie dans un plugin de bloc sont-ils ignorés?

2) Si oui, existe-t-il un moyen de contourner cela, devons-nous l'ajouter à un élément enfant du tableau de génération?

3) Si le contexte ajouté directement est ignoré, l'ajout d'un getCacheContexts () est-il la voie à suivre pour les plugins de bloc dans les modules personnalisés?

13
oknate

Dans la plupart des cas, vous définissez simplement le contexte de cache directement sur le tableau de rendu que vous renvoyez dans votre méthode build ().

J'ai enfin trouvé quel était mon problème, avec l'aide de @Berdir et @ 4k4. Si vous utilisez un modèle personnalisé, tel que block - myblock.html.twig et que vous sortez les variables individuellement, telles que {{content.foo}} au lieu de toutes en même temps comme {{content}}, il ignore vos contextes de cache sont passés directement dans votre tableau de génération de blocs, lorsqu'ils sont déconnectés. Voir Quelle est la bonne façon de définir des contextes de cache sur des blocs personnalisés?

Donc, pour répondre à la question d'origine:

1) Les contextes de cache passés directement dans un plugin de bloc personnalisé sont parfois ignorés. Vous pouvez tester cela en en modifiant le SyndicateBlock , puis en créant un modèle personnalisé dans votre bloc de thème - syndicate.html.php dans lequel vous éditez les variables individuellement comme ceci:

{% block content %}
  {{ content.foo }}
{% endblock %}

Lorsque vous modifiez les arguments d'URL, vous verrez que le bloc ne respecte pas le contexte du cache.

Maintenant, si vous le changez en sortie tout le contenu en tant que morceau, cela fonctionne:

{% block content %}
  {{ content }}
{% endblock %}

Maintenant, il respecte le contexte du cache et le bloc est unique par page.

2) Pour l'instant, pour contourner ce problème, vous pouvez simplement afficher le contenu de votre bloc dans son propre modèle.

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

  }

Cela contourne les exceptions de mise en cache ésotérique du module de blocage et votre formulaire est désormais unique par page lorsqu'il est déconnecté.

3) Devriez-vous créer votre propre modèle de thème pour résoudre ce problème, ou simplement ajouter une méthode pour getCacheContexts () dans votre plugin Block personnalisé? Il est préférable de créer un nouveau modèle de thème, plutôt que d'ajouter une méthode getCacheContexts () qui remplace l'ordre naturel de propagation des contextes de cache et peut casser les métadonnées plus profondément dans votre tableau de génération.

9
oknate

Pour tous ceux qui trouvent cela ...

La raison pour laquelle le rendu content (ou content|without()) fonctionne est qu'il y a un élément dans le tableau de rendu content['#cache'] qui contient toutes les métadonnées pouvant être mises en cache pour le contenu.

Si vous ne permettez pas que cela soit rendu dans une brindille, content ou {{'#cache': content['#cache']|render }}, la page ne sait pas qu'elle contient des métadonnées pouvant être mises en cache (par exemple, elle ne bouillonne jamais).

On dirait que vous ne faites pas de personnalisation twig cependant. Si vous utilisez quelque chose comme Varnish, cela pourrait également être un coupable pour les utilisateurs anonymes.

4
twill

J'ai également rencontré ce problème et la solution de contournement que j'ai trouvée était de créer une nouvelle variable block_content basée sur une version filtrée de la variable de contenu principale à l'exclusion des champs personnalisés que je souhaite rendre manuellement:

{% set block_content = content|without('field_mycustomfield', 'field_mycustomfield2') %}

Ensuite, au lieu de rendre la variable "content.body" directement plus tard, j'appelle:

{{ block_content }}

Si vous souhaitez rendre chaque champ individuellement, vous pouvez simplement continuer à les ajouter au filtre "sans" afin que le rendu block_content ne fasse rien, sauf la mise en cache fixe.

3
Ryan Barkley

La méthode la plus simple pour y parvenir est de déclarer et de définir la méthode 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'];
  }

Consultez la documentation CacheableDependency , elle devrait contenir tout ce dont vous avez besoin;)

0
Ben Cassinat