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

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter 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. Dans 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ée 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 liste 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(). Il n’a presque pas changé depuis Drupal 7.  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. Petite nouveauté dans ce cas par rapport à Drupal 7 : 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, de manière bien plus simple que dans Drupal 7 il est possible de dériver des templates. En effet, chaque double underscore dans le nom d’une clé 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.

À propos de Julien

Gérant - 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.