Drupal 8 - Gestion du cache - Les lazy builders

19/02/2019

Je vais vous présenter ici les lazy builders. Cette technique vous permet de rendre du contenu non mis en cache. Si vous n'avez pas pris connaissance des articles précédents, je vous encourage à lire l'introduction et la notion des contextes de cache avant de commencer.

 

Quand utiliser des lazy builders ?

Comme dit dans l'introduction, les lazy builders permettent de rendre du contenu qui n'est pas mis en cache. Autrement dit, ce contenu sera calculé et rendu à chaque fois qu'il sera demandé. Le temps de calcul et de rendu étant en général très couteux, il est nécessaire de ne pas utiliser cette méthode à outrance. En effet, les lazy builders sont extrêmement utiles pour générer des contenus ponctuels et dont le dynamisme est soumis à un nombre de contextes de cache trop important pour vouloir l'en soustraire. La phrase précédente étant un peu trop pompeuse, je vais revenir dessus.

La mise en cache est un procédé qui est aujourd'hui inévitable car il permet un accroissement des performances considérables. L'idée des lazy builders est de venir insérer des contenus dynamiques à l'intérieur d'un contenu statique mis en cache. Ainsi on conserve des performances acceptables pour les contenus statiques. 

Là où les lazy builders apportent une plus value par rapport à la génération de rendu lié à un contexte de cache, c'est dans les cas où les contextes sont trop nombreux et où les rendus sont délivrés très rarement. En effet, si vous avez lu l'introduction sur la notion de cache de Drupal 8, vous savez que la mise en cache est couteuse en ressource (en base de données) et que si un contexte n'est généré qu'une seule fois, il ne sert à rien de mettre en cache un contenu. Par exemple, dans une gestion de liste d'éléments qui peuvent être filtrés par un grand nombre de critères, on ne peut pas mettre en cache l'ensemble des possibilités des critères sélectionnés par les utilisateurs. Une combinaison de critères générant un contexte de cache, il est inutile de stocker un rendu pour une combinaison qui est propre à un seul utilisateur. C'est dans un tel cas qu'on va pouvoir utiliser les lazy builders.
 

 

Mettre en place un lazy builders

Imaginons qu'on reprenne l'exemple de la gestion de contexte, où on affichait un message selon l'heure de la journée. On mettait en cache deux rendus possibles, affichés selon si la page était vue entre 3h et 17h59 ou entre 18h et 2h59. On va ajouter à ce message, l'heure d'affichage. Comme expliqué précédemment, on ne va pas générer autant de cache que de fois où le message est affiché donc on va utiliser un lazy builder. 

Pour cela, on va ajouter un placeholder dans le build array de l'élément qui va contenir les données dynamiques. Ce placeholder sera remplacé par le contenu retourné par une fonction associée au placeholder.

On va donc commencer par créer le service qui va calculer et retourner le rendu dynamique dans politesse.services.yml : 

services: 
  politesse.hour_manager:
    class: \Drupal\politesse\Service\HourManager

Dans ce service, on va ajouter la méthode getHour() : 

<?php
namespace Drupal\politesse\Service;

/**
 * Définit un service qui retourne l'heure.
 *
 * Cache context ID: 'politesse_cache_context'.
 */
class HourManager {
  
  /**
   * L'identifiant du service
   *
   * @const string
   */
  const SERVICE_ID = 'politess.hour_manager';

  /**
   * Retourne le service
   * 
   * @return static
   *   Le service.
   */
  public static function me(){
    return \Drupal::service(static::SERVICE_ID);
  }

  /**
   * {@inheritdoc}
   */
  public static function getHour() {
    return date('H:i:s');
  }

}

 

Ensuite, on va ajouter la notion de placeholder dans le build array de l'élément dans lequel on veut rendre le contenu dynamique, dans politesse.module : 

<?php

use \Drupal\politesse\Cache\Context\PolitesseCacheContextIdentifier;

/**
 * Implements hook_preprocess_node().
 */
function politesse_preprocess_node(&$variables) {
  {...}

  // On crée l'identifiant du lazy_builder.
  $id = static::SERVICE_NAME;
  $callback = $id . ':getHour';
  // On passe l'id du noeud en paramètre.
  $variables['#attached']['placeholders'][$id] = [
    '#lazy_builder' => [$callback]
  ];
  // On ajoute le placeholder dans les variables du template.
  $variables['hour']['#markup'] = $id;
}

La première partie consiste bien à créer un identifiant de placeholder et sa callback. La déclaration de la callback suit la pattern [service_id]:[callback].

Une fois que cet identifiant est ajouté aux placeholders, on ajoute l'identifiant aux variables. Ainsi on rendra directement l'identifiant dans le template twig. Ce rendu sera bien mis en cache, cependant après la récupération de l'élément dans le cache, l'identifiant sera remplacé par le résultat de la méthode associé à l'identifiant via le tableau placeholder. On a donc un contenu totalement dynamique rendu dans un contenu mis en cache.

 

Pour aller plus loin, vous pouvez tout à fait passer des paramètres à la callback via les placeholders. Par exemple :

$variables['#attached']['placeholders'][$id] = [
    '#lazy_builder' => [$callback, [$arg1, $arg2]]
  ];

 

Conclusion

Voilà, au final on a ajouté une partie dynamique à un contenu statique. Encore une fois cette méthode est facile à mettre en place lorsqu'on veut se soustraire au cache. Cependant elle doit être utilisée avec précaution puisqu'elle ne doit être mise en place que dans des cas de derniers recours. En effet elle ne doit pas être mise en place pour éviter d'avoir à invalider des caches manuellement, chose qui peu complexe et vite brouillon dans certains cas de grosses dépendances d'éléments.

 

Ajouter un commentaire

HTML restreint

  • Balises HTML autorisées : <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et les adresses courriel se transforment en liens automatiquement.
Votre email ne sera pas publié mais permettra à l'administrateur de vous recontacter en cas de problème