Lazy builder, l'affichage que le système de cache de Drupal n'aura pas !

Un profil utilisateur avec différents boutons
Comment contourner le système de cache de Drupal pour gérer l'affichage d'éléments plus dynamiques que le contenu principal

Problématique

Prenons le cas d'un profil utilisateur. Lorsqu'il est visionné par le propriétaire du compte, on veut afficher un lien pour lui permettre de modifier son profil. Et lorsque ce même profil est visionné par d'autres visiteurs, on veut afficher des liens de mise en relation (demande d'ajout et envoi de message). Bref, on veut ce qu'il y a dans l'illustration ci-dessus.

J'ai d'abord pensé m'en sortir avec un simple {% if %} dans le template Twig de l'entité user, mais lorsque l'utilisateur propriétaire du profil visite sa propre page, elle est mise en cache et n'est pas "rafraichie" lors de la visite d'autres utilisateurs : ils voient donc le lien d'édition et non pas les liens de mise en relation...

 

Le choix de la solution

On peut envisager plusieurs solutions, chacune a ses inconvénients :

  • ne pas mettre en cache la page utilisateur du tout ==> on perd l'intérêt de la mise en cache, alors que la majorité du profil (photo, nom, prénom, présentation, etc.) est affiché de la même manière à tous les utilisateurs
  • ajouter le contexte de cache "user" à toute la page du profil utilisateur ==> on met en cache beaucoup de choses pour chaque utilisateur, ce qui surcharge inutilement la base de données
  • gérer l'affichage des liens dans un bloc (block plugin) auquel on ajouterait le contexte de cache "user" ==> même inconvénient qu'au dessus
  • utiliser un lazy builder ==> on ne met pas du tout en cache cette petite partie du profil utilisateur, il faut donc réserver l'utilisation de cette technique pour exécuter une portion de code très rapide, en évitant le plus possible d'accéder à la base de données... mais avec le module BigPipe activé, cette contrainte n'est plus vraie, car cette partie d'affichage sera calculée et chargée en arrière plan.

 

Qu'est ce qu'un lazy builder ?

Le lazy builder c'est (tl;dr lire uniquement les mots en gras) :

  • un élément que l'on ajoute via un hook au render array (de l'entité "user" dans notre cas) et qui fait référence à...
  • une fonction de rappel (callback) qui est appelée à chaque affichage et retourne un "render array"
  • une variable que l'on place dans le template (celui de l'entité "user" dans notre cas) et qui sert de "placeholder"

C'est donc assez simple et rapide à implémenter, donc sans plus de blabla voici le code pour l'entité user.

 

Hook, callback et placeholder

my_module.module

use Drupal\Core\Url;

/**
 * Implements hook_preprocess_user().
 */
function my_module_preprocess_user(&$vars) {
  $user = $vars['elements']['#user'];

  $vars['user_profile_links'] = [
    '#create_placeholder' => TRUE,
    '#lazy_builder' => [
      '_my_module_user_lazy_builder_profile_links',
      [$user->id()],
    ],
  ];
}

/**
 * A lazy builder callback to provide user profile links.
 *
 * @param int $displayed_uid
 *   A user ID
 *
 * @return array
 *   Render array
 * */
function _my_module_user_lazy_builder_profile_links($displayed_uid) {
  $links = [];

  $current_user = \Drupal::currentUser();
  $current_uid = $current_user->id();

  if ($current_uid == $displayed_uid) {
    $links[] = [
      '#type' => 'link',
      '#title' => t('Edit my profile'),
      '#url' => Url::fromRoute('entity.user.canonical', ['user' => $current_uid]),
    ];
  } else {
    $links[] = [
      '#type' => 'link',
      '#title' => t('Add to my friends'),
      '#url' => Url::fromUserInput('/network/add-friend'),
    ];
    $links[] = [
      '#type' => 'link',
      '#title' => t('Send a message'),
      '#url' => Url::fromUserInput('/network/send-message'),
    ];
  }

  return $links;
}

Dans le module, on défini 2 fonctions :
- le hook my_module_preprocess_user() permet d'ajouter l'élément lazy builder au render array de l'entité user
- le callback _my_module_user_lazy_builder_profile_links() contient la logique d'affichage des liens du profil

 

user.html.twig

<article{{ attributes.addClass('user-profile') }}>

  {% if content %}
    {{- content -}}
  {% endif %}

  {{ user_profile_links }}

</article>

Dans le template, on place la variable user_profile_links qui a été définie plus haut dans le hook.

Commentaires

Permalien

J'ai pas encore pris le temps de vérifier, mais à la DrupalCon Europe 2020, j'ai vu une présentation où tu peux aussi mettre en cache ce que va te renvoyer le #lazy_builder et du coup il n'y aura que cette partie là qui sera dans un cache qui varie beaucoup tout en permettant au reste de la page de ne pas avoir un cache qui varie énormément.

https://github.com/joachim-n/drupalcon-europe-2020-lazy-builders/blob/m…

Permalien

Je viens de passer sur Drupal 10 sur un site et j'ai voulu implémenter un lazy builer en prenant modèle sur cet article et ça ne fonctionne plus, je ferais une mise à jour dès que possible...
 

Drupal\Core\Security\UntrustedCallbackException : Render #lazy_builder callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was _mymodule_lazy_builder_mycallback. See https://www.drupal.org/node/2966725 dans Drupal\Core\Render\Renderer->doTrustedCallback() (/var/www/html/web/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php ligne 106)

 

Ajouter un commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.

Texte brut

  • Aucune balise HTML autorisée.
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et les adresses courriel se transforment en liens automatiquement.