Le piège du cache tag 'node_list' dans les views de Drupal8

Le cache tag node_list est géneré automatiquement lorsque nous créons une vue qui liste des entititées de type node sous Drupal 8. Ce cache tag va invalider le cache de toutes les vues qui listent n'importe quel sorte de nodes quand nous réalisons une opération CUD (create, update, delete) sur des nodes.

A première vue cela semble une excellente stratégie d'invalidation du cache vu que lorsque nous modifions un node lors d'une opération CUD, le cache de toutes les vues qui affichent des nodes sera invalidé pour pouvoir montrer la dernière modification.

node_list cache tag views drupal9

(pour voir les cache tags dans votre header, habilitez votre settings.local.php)

Jusqu'ici tout va bien, mais... Que se passerait-il si nous avions un site à fort trafique avec des dizaines de types de nodes différents et des centaines de vues qui afficheraient différents type de nodes?

Dans ce cas là, si nous modifions un node (disons une page), le cache de toutes les vues qui affichent des nodes de type page (mais aussi de type articles et tout autre type de node bundles) sera invalidé. Cela veut dire que si nous modifions une page, le cache de toutes les vues qui affichent des articles sera lui aussi invalidé, même si aucun article n'a été modifié. Cela pourrait entraîner un sérieux problème de performance surtout si nous avons beaucoup de mises à jour comme sur un site d'un journal en ligne.

Comment pourrions-nous invalider le cache des views de manière plus spécifique, par exemple seulement pour des nodes du même type?

Pour ce faire nous allons procéder en deux étapes:

- Placer un cache tag personnalisé (custom) pour toutes les vues qui affichent le même type de node, comme node:type:page pour toutes les vues qui affichent des nodes de type page, node:type:article pour les vues qui affichent des nodes de type article et ainsi de suite...
- Invalider ces custom cache tags après qu'une opération CUD soit réalisée sur un type de node spécifique grâce au hook hook_node_presave() 

Ajouter des custom cache tags avec le module Views Custom Cache Tags

Heureusement, pour résoudre le problème du node-list cache tag, la communauté Drupal a déjà fourni un module qui nous permet d'ajouter des cache tags personnalisés à nos vues: le module Views Custom Cache Tags.

Dans ce cas précis, nous allons ajouter à nos vues un cache tag personnalisé pour chaque type de node que nous devrons afficher:
node:type:article pour les vues qui listent des articles
node:type:page pour les vues qui listent des pages
node:type:<votre custom node type> ....

Tout d'abord, nous allons télécharger et installer le module.

# Télécharger le module avec composer
composer require drupal/views_custom_cache_tag
# Installer le module avec Drupal Console
drupal moi views_custom_cache_tag 

Ensuite nous allons créer deux vues de block, une qui affichera 5 articles (Block Articles) et une autre qui affichera 5 pages (Block Pages). Une fois les vues en place, nous allons ajouter un custom cache tag node:type:article dans la vue qui affiche des articles (Block Articles). Faites la même chose avec un autre custom cache tag comme  node:type:page dans la vue qui affiche des pages (Block Pages).

Voyons comment faire pour la vue (block) qui affiche des articles:

1. Éditez la vue
2. Allez sur ADVANCED et cliquez sur 'Tag Based' de Caching

custom cache tags views drupal8

3. Cliquez sur Custom Tag based

custom cache tags views drupal 8

4. Ajouter le cache tag personnalisé pour ce type de node. Dans notre cas, comme nous affichons des articles, nous introduisons node:type:article. Pour les vues qui affichent d'autres type de node, nous introduirons node:type:<node-type>. N'oubliez-pas de cliquer sur Apply quand vous avez terminé.

custom cache tags views drupal8

5. Sauvegardez la vue

custom cache tags views Drupal 8

Lorsque nous avons placé tous les cache tags personnalisés sur toutes nos vues qui affichent des nodes, nous pouvons passer à la deuxième étape: invalider ces cache tags personnalisés.

Invalider les custom cache tags avec hook_node_presave

À présent nous devons invalider ces cache tags personnalisés quand une action CUD est réalisée sur un type de node spécifique grâce au hook hook_node_presave.

Pour ce faire, nous allons créer un module. Vous pouvez télécharger le code de ce module ici.

Tout d'abord nous créons notre module avec Drupal Console comme suit:

drupal generate:module  --module="Invalidate custom cache tags" --machine-name="kb_invalidate_custom_cache_tags" --module-path="modules/custom" --description="Example to invalidate views custom cache tags" --core="8.x" --package="Custom"  --module-file --dependencies="views_custom_cache_tag"  --no-interaction

Ensuite nous l'habilitons aussi avec Drupal Console:

drupal moi kb_invalidate_custom_cache_tags

À présent nous pouvons éditer notre fichier kb_invalidate_custom_cache_tags.module et placer le hook suivant:

// For hook_ENTITY_TYPE_presave.
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;

/**
 * Implements hook_ENTITY_TYPE_presave().
 *
 * Invalid cache tags for node lists.
 */
function kb_invalidate_custom_cache_tags_node_presave(EntityInterface $entity) {
  $cache_tag = 'node:type:' . $entity->getType();
  Cache::invalidateTags([$cache_tag]);
}

Et oui, il y a encore des hooks sous Drupal8... Ce hook sera lancé chaque fois qu'un node sera créé ou modifié. Ensuite nous récupérons le type de node (page, article, ...) avec $entity->getType() et créons la variable $cache_tag avec cette valeur, la variable correspondra dès lors à notre custom cache tag pour ce type de node. Enfin nous marquons ce cache tag dans tous les 'bins' comme invalide avec Cache::invalidateTags([$cache_tag]); Ainsi le cache de toutes les views qui ont ce custom cache tag sera invalidé.

Dans ce cas, si nous ajoutons ou modifions un node de type article, le custom cache tag serait node:type:article et le cache de toutes les vues qui affichent des articles sera invalidé. Le cache des vues qui ont un autre custom cache tag comme node:type:page restera valide. C'est justement ce que nous cherchions! Merci Drupal!

En résumé.

Afin d'éviter que le cache de toutes nos vues qui affichent des nodes ne soit invalidé lorsque nous réalisons une opération CUD sur des nodes, nous devons remplacer le cache tag par défaut node_list par un cache tag personnalisé (custom) plus spécifique pour chaque type de node comme node:type:<node-type>.

Grâce au module contrib Views Custom Cache Tags nous pouvons ajouter des custom cache tags dans nos vues sur base du type de node que nous voulons afficher comme node:type:article, node:type:page et ainsi de suite.

Ensuite, nous marquons ces custom cache tags comme invalides quand nous créons ou modifions un node grâce au hook hook_ENTITY_TYPE_presave que nous avons placé dans notre module custom.

Voilà. Ce n'était pas trop compliqué à mettre en oeuvre non? Si vous avez une autre stratégie pour faire face au problème du node_list cache tag, vous pouvez la partager avec nous dans les commentaires.