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
J'ai pas encore pris le…
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…
Ça flingue complètement le…
Ça flingue complètement le titre de mon article mais c'est une super info !! Merci Florent ;-)
ça ne marche plus
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...
Il faut implémenter …
Il faut implémenter TrustedCallbackInterface et déclarer la fonction trustedCallbacks afin de déclarer notre callback du lazy_builder.
Ajouter un commentaire