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

13

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

Ce 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 m'embrouille, car il semble que le moyen d'ajouter la mise en cache dans Drupal 8 est d'ajouter un #cache élément 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?

oknate
la source
1
1) Non, votre contenu de bloc est en fait un niveau inférieur et devrait être fusionné plus tard. 2) Pas nécessaire car 1, 3) L'implémentation de getCacheContexts () peut être plus facile / plus propre mais ne devrait pas être requise. Vous mentionnez explicitement les utilisateurs anonymes, êtes-vous sûr que cela n'affecte pas non plus les utilisateurs authentifiés normaux? Le problème disparaît-il si vous désactivez dynamic_page_cache? Quelque chose d'étrange doit se produire s'il n'affecte que les utilisateurs, car le cache de page interne varie toujours selon les arguments d'URL / requête.
Berdir
1
La désactivation du cache de page dynamique ne résout pas le problème.
Oknate
1
Hm, Cela pourrait être un problème avec le fait que votre élément de niveau supérieur ne contient rien sauf #cache. Avez-vous essayé de simplement définir #cache dans votre formulaire? C'est la forme qui doit varier en fonction de ceux-ci, et puisque les balises de cache bouillonnent, cela devrait simplement fonctionner. Et si jamais vous utilisez votre formulaire dans un autre bloc ou un autre endroit, il devrait également y fonctionner.
Berdir
1
J'ai rapidement piraté SyndicateBlock et utilisé cette méthode build (): gist.github.com/Berdir/33a31b1e98caf080dae78adb731dba4c . Placer que sur mon site fonctionne très bien, les contextes de cache sont visibles dans la table cache_render et l'URI de demande correct est affiché. Pouvez-vous essayer la même chose? Il me semble que quelque chose de très étrange se passe sur votre site
Berdir
2
Utilisez-vous le modèle de bloc standard ou un modèle personnalisé? Voir drupal.stackexchange.com/questions/217884/…
4k4

Réponses:

9

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 modifiant le SyndicateBlock , puis en créant un modèle personnalisé dans votre bloc de thème - syndicate.html.php dans lequel vous sortez 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.

oknate
la source
Ceci est un très bon résumé du problème. Mais je pense que la conclusion en 3) est problématique, car non seulement vous brisez que vos propres métadonnées de cache peuvent bouillonner, mais aussi celles qui peuvent être plus profondes à l'intérieur du tableau de rendu et vous ne le savez peut-être pas.
4k4
Vous proposeriez donc de créer un nouveau modèle de thème? Pour conserver un bouillonnement clair?
Oknate
Oui, utilisez le modèle de bloc uniquement pour ajouter des éléments à l'extérieur. Construisez l'intérieur du bloc dans build (). Utilisez des modèles personnalisés pour cela ou utilisez des éléments de rendu, comme un tableau ou un modèle principal comme des liens.
4k4
OK, je mettrai à jour la réponse.
Oknate
Ugh, je ne savais pas que l'exploration de Twig ignorerait les métadonnées en cache. Cela peut signifier que nous devons utiliser notre propre méthode personnalisée à la fin pour explorer (ce qui rend l'extension de brindille inutile) afin de préserver les métadonnées tout en descendant dans les niveaux inférieurs. Bonne trouvaille!
LionsAd
4

Pour tous ceux qui trouvent cela ...

La raison pour laquelle le rendu content(ou content|without()) fonctionne est qu'il existe 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 n'autorisez pas le rendu dans une brindille, soit contentou {{'#cache': content['#cache']|render }}, la page ne sait pas qu'elle contient des métadonnées en cache (par exemple, elle ne bouillonne jamais).

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

sergé
la source
3

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 veux 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.

Ryan Barkley
la source
0

La méthode la plus simple pour y parvenir est de déclarer et de définir la getCacheContexts()méthode


  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 de CacheableDependency , elle devrait contenir tout ce dont vous avez besoin;)

Ben Cassinat
la source
Cela ne fonctionne plus de la façon dont les blocs sont rendus maintenant, voir drupal.stackexchange.com/questions/288881/…
4k4