Drupal 10 : L'API de rendu (Render API)

L'API de rendu permet de décrire le contenu d'une page sans utiliser de HTML. Cela permet de retarder au maximum le moment de la conversion en HTML pour simplifier les modifications.

Cet article a été initialement rédigé pour Drupal 8 mais son contenu est toujours d'actualité pour Drupal 9 et Drupal 10.

N'hésitez pas à nous contacter ou à vous inscrire à notre formation «Drupal pour les développeurs et développeuses» pour en savoir plus !

Afin de garder le plus de flexibilité possible dans la gestion du rendu, Drupal s’appuie sur le principe des Render arrays ou Renderable arrays introduit en D7. Depuis Drupal 8 on a complètement remplacé l’appel à la fonction theme(). L’idée est d’assembler chaque portion de la page dans un tableau décrivant le type et le contenu de chaque donnée. En retardant au maximum le moment où ce tableau est converti en HTML, il devient possible pour chaque module de modifier la mise en forme des données à sa convenance.

Les tableaux se multipliant au vue du nombre d’éléments constituant une page, on peut comparer l’agglomérat de ces renderable arrays à un arbre de rendu qui serait l’équivalent du DOM d’une page web.

Il ne faut pas confondre l’API de rendu et l’API des formulaires même s’ils utilisent le même principe de tableaux décrivant leur contenu avec des propriétés.

Un renderable array combinant plusieurs éléments ressemble à cela :

$page = [
  '#type' => 'page',
  'content' => [
    'system_main' => [],
    'another_block' => [],
    '#sorted' => TRUE,
  ],
  'sidebar_first' => [],
];

Voici un exemple le plus simpliste possible d’un render array :

$render_array = ['#markup' => "<p>Some text</p>"];

Les propriétés que l’on trouve le plus souvent au sein d’un renderable array sont les suivantes :

#type

Valeur correspondant à un RenderElement (voir ci-dessous).

#cache

Attributs pour la cachabilité du tableau (voir chapitre sur le cache).

#markup

Valeur simple pour du HTML.

#prefix / #suffix

Chaîne à afficher avant / après le rendu du tableau.

#pre_render / #post_render

Tableaux de fonctions exécutées avant le rendu des données ou après. Fait pour modifier des données (#pre_render) ou ajuster l’affichage des données (#post_render).

#theme

Nom de la clé de thème à utiliser pour mettre en forme les données qui lui sont passées.

#theme_wrappers

Tableau de fonctions de thème à utiliser pour gérer le rendu autour des enfants.

C’est l’id des Plugins de type RenderElement qui dicte les différentes valeurs pouvant être données à l’attribut #type. Ces plugins permettent d’avoir une notation raccourcie de renderable array. On verra par exemple le #type more_link qui permet d’afficher un lien “Voir plus” sans avoir à écrire trop de lignes dans le tableau. N’hésitez pas à créer les vôtres si vous avez à manipuler les structures complexes de vos données.

Dans la majorité des cas, vous ne spécifierez aucun #type et utiliserez #theme pour mettre en forme vos données.
Les clés de thème sont à déclarer via le hook_theme(). Quand des variables sont nécessaires pour afficher leur contenu, il suffit de les passer comme propriétés supplémentaires pour les transmettre comme dans l’exemple qui suit.

# theme.inc
# Exemple de déclaration

function drupal_common_theme() {
  return array(
    'item_list' => array(
      'variables' => array(
        'items' => array(),
        'title' => '',
        'list_type' => 'ul',
        'wrapper_attributes' => array(),
        'attributes' => array(),
        'empty' => NULL,
        'context' => array()
      ),
    ),
  );
}
# BookController.php
# Exemple d’utilisation

public function bookRender() {
 $book_list = array();
 foreach ($this->bookManager->getAllBooks() as $book) {
  $book_list[] = $this->l($book['title'], $book['url']);
 }
 return array(
  '#theme' => 'item_list',
  '#items' => $book_list,
  '#cache' => [
   'tags' => \Drupal::entityManager()->getDefinition('node')->getListCacheTags(),
  ],
 );
}

Il ne faut pas confondre un type de rendu et une clé de thème. Le premier va vous permettre de manipuler des données et de conserver un render array là où la seconde va vous retourner du HTML.

Suggestions, wrappers et dérivés

Les clés de thème peuvent aussi être utilisées pour générer le rendu d’un ou plusieurs wrappers. On utilise dans ce cas l’attribut #theme_wrappers d’un render array. Chaque entrée de #theme_wrappers peut désormais surcharger les valeurs passées au #theme principal si nécessaire. Exemple :

# anywhere.php

$build = [
  '#theme' => 'my_theme_function',
  '#some_var' => 'Some value',
  '#other_var' => 'Other value',
  '#theme_wrappers' => [
    'first_wrapper_theme_function',
    'second_wrapper_theme_function' => [
      '#some_var' => 'Overriden value',
    ],
  ],
];

Dans l’exemple ci-dessus, le template first_wrapper_theme_function recevra les paramètres “Some value” et “Other value” alors que le template second_wrapper_theme_function recevra “Overriden value” et “Other value”.

Pour finir, il est possible de dériver des templates. En effet, chaque double underscore dans le nom d’une clé de thème que l’on tente d’invoquer est considéré comme un séparateur. Ainsi, si le nom complet de la clé de thème n’est pas déclaré, on retire la dernière partie et on continue ainsi jusqu’à retomber sur une clé de thème existant. Côté noms de fichiers de template, on remplace les underscores par des tirets.
Par exemple $build = ['#theme' => 'my_theme_function__node__article']; tenterait d’abord de charger my-theme-function--node--article.html.twig, puis my-theme-function--node.html.twig et enfin my-theme-function.html.twig.

Pour chaque clé de thème, il est possible d’implémenter le hook hook_theme_suggestions_HOOK() afin de proposer des suggestions en fonction des variables passées au template.

Par exemple :

# my_module.module

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function my_module_theme_suggestions_node(array $variables) {
  $suggestions = [];
  if ($variables['#node']->type == 'article') {
    $suggestions[] = 'node__article__' . $variables['#node']->field_article_type->value;
  }
  return $suggestions;
}

Le rendu final (conversion des Render arrays vers du HTML) est effectué par des Plugins de type Renderer. Le cœur définit 4 types de Renderer : HTML, AJAX, Dialog et Modal. Dans la plupart des requêtes, la réponse retournera du HTML. Sa génération se passe en 3 grandes étapes.

  • La première consiste à passer dans HtmlRenderer::prepare() qui s’assure que les données sont formatées correctement (présence d’un #type => ‘page’, ajout des attachments si besoin).
  • La seconde se déroule dans HtmlRenderer::renderResponse() qui encapsule le cœur de la page dans un #type => ‘html’ pour avoir les tags <html>, <head>, <body>, <scripts>, etc.
  • Et enfin on passe tout cela dans la classe Renderer qui se charge de sortir tout cela en HTML avant de le renvoyer comme réponse (Response).

Le cheminement complet de rendu d’une page est décrit dans cette page de documentation https://www.drupal.org/developing/api/8/render/pipeline, je vous invite à la consulter si vous êtes curieux.

Alors, quand utiliser les render arrays ? C’est simple, tout le temps ! Si vous êtes en train de générer une réponse qui met en forme des données, vous devez utiliser un render array.

Votre commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
Votre adresse servira à afficher un Gravatar et à vous notifier des réponses. Votre commentaire sera anonymisé si ce billet est dépublié pendant plus de 3 mois.
Pour lutter contre le spam notre système enregistre votre adresse IP et votre adresse e-mail si vous la partagez.
Nous vous invitons à consulter notre politique de confidentialité pour comprendre les traitements faits de ces données et comment les rectifier.

À propos de Julien

Co-fondateur - Scrum master & Expert technique

Utilisateur de Drupal depuis 2008, j’ai fait mes armes comme développeur chez Commerce Guys puis me suis mis à encadrer les nouveaux arrivants avant de donner des formations, participer aux avant ventes et accompagner les équipes au passage à Scrum.

Je suis impliqué dans la communauté française de Drupal depuis 2009, j’ai été tour à tour président puis vice-président de l’association Drupal France et francophonie entre 2011 et 2013.