Les vues Twig

Les vues Twig

1 mai 2021

Les vues ou templates permettent de séparer le code HTML du code PHP. Ce sont elles qui s'afficheront aux yeux de tous ! C'est la partie visible de l'iceberg. Vous pourrez faire parler votre talent de designer, graphiste, ébéniste... ?

Pour la partie "vue" de votre projet, Symfony intègre un moteur de template nommé Twig. Ce moteur propose son propre langage. La syntaxe est facile à appréhender et ne perturbe pas le code HTML.

Il intègre des sécurisations sur nos variables de manière automatique et se charge de mettre vos pages en cache à leur première exécution. Ceci permet d'améliorer le chargement de vos pages. ?

Twig regroupe un ensemble de fonctions et de filtres pour faciliter le développement de vos pages sans avoir besoin de réinventer la roue.

Structure d'un fichier Twig

Sous Symfony, tous nos fichiers de vue se situent dans le dossier "templates", à la racine du projet.
Voici la structure d'un fichier Twig :

{# Template général de la page #}
{% extends 'base.html.twig' %}

{# Titre de la page #}
{% block title %}Mon titre{% endblock %}

{# Ici vous mettez vos codes HTML et balises Twig au besoin #}
{% block body %}
    <p>Mon contenu dans ce block</p>
{% endblock %}

Détaillons un peu ce fichier. En première ligne, nous avons un "extends" suivi du nom d'un fichier "base.html.twig". Tout comme en PHP POO, "extends" représente un héritage d'un autre fichier. Je reviendrai dessus un peu plus tard dans l'article.

Il est suivi d'un {% block title %}. Ce bloc permet de donner un titre à notre page, c'est l'équivalent en HTML à :

<title>Mon titre</title>

Enfin, nous avons un {% block title %} qui est la partie la plus importante de ce fichier, car c'est ici que la magie opère ?‍♂️. C'est à l'intérieur de ce bloc que vous mettrez vos codes HTML tel que vous le feriez normalement. La différence est que vous ne précisez pas la balise html, les métas et body. Il s'agit juste du code HTML se trouvant dans la balise <body> d'un fichier HTML habituel.

Template général

Il est très fréquent qu'un site Web ou une application possède des caractéristiques que l'on retrouve sur plusieurs pages comme une barre de navigation, un header, un footer...

Pour éviter une répétition non nécessaire ou un vulgaire copier-coller, Twig intègre un système de template général ou layout, que nous appelons avec un "extends"

{% extends 'base.html.twig' %}

Le code ci-dessus est celui que vous retrouverez à la génération d'un nouveau contrôleur et donc d'un fichier de vue par défaut.

Si vous regardez à la racine de votre dossier "templates", vous trouverez un fichier nommé "base.html.twig".

En regardant son contenu, vous verrez une différence avec la structure habituelle d'un fichier Twig :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

Il s'agit de la base par défaut d'un fichier de vue, c'est ici que nous mettrons tous nos codes à répétitions. Rappelez-vous : barre de navigation, footer... On y retrouve aussi du code Twig et notamment des blocs.

Je vous ai dit plus haut que "extends" est comme en PHP POO, c'est un héritage et c'est exactement comme ça que le système va fonctionner. Le fichier de vue va hériter du fichier de base et remplir les parties du squelette grâce aux blocs.

Le fichier "base.html.twig" va récupérer le contenu du bloc title et le placer en lieu et place du sien et faire la même chose pour le bloc body. Cela vous donnera un fichier HTML complet et propre. Pour ce qui est des blocs stylesheets et javascripts, il ne remplira rien, car ces blocs n'existent pas dans le fichier "index.html.twig" et il fera de même avec title et body s'ils sont manquants.

Le mieux étant que je vous donne un exemple concret.

Voici le contenu de mon fichier "base.html.twig" :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        <nav><!-- Contenu de ma navbar --></nav>

        {% block body %}{% endblock %}

        <footer><!-- Contenu de mon footer --></footer>

        {% block javascripts %}{% endblock %}
    </body>
</html>

Le contenu de mon fichier "index.html.twig" :

{# Template général de la page #}
{% extends 'base.html.twig' %}

{# Titre de la page #}
{% block title %}Mon titre{% endblock %}

{# Ici vous mettez vos codes HTML et balises Twig au besoin #}
{% block body %}
    <h1>Mon super titre H1</h1>
    <p>Un paragraphe contenant pleins de mots à lire</p>
{% endblock %}

Une fois la page traitée par Twig, le résultat sera celui-ci :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Mon titre</title>
    </head>
    <body>
        <nav><!-- Contenu de ma navbar --></nav>

        <h1>Mon super titre H1</h1>
        <p>Un paragraphe contenant pleins de mots à lire</p>

        <footer><!-- Contenu de mon footer --></footer>
    </body>
</html>

Vous pouvez créer autant de layout que nécessaire, selon vos besoins bien entendu. Il suffit de changer le bloc extends en précisant le layout que vous souhaitez utiliser pour la vue.

Qu'en est-il des blocs stylesheets et javascripts ?

Ces deux blocs sont très intéressants car ils vont permette d'intégrer et d'exécuter du CSS et/ou du Javascript dans un fichier de vue au moment de son affichage et pas avant. Pratique si vous souhaitez traiter du code Javascript à un moment précis par exemple.

Tout d'abord revenons à notre layout "base.html.twig". Dans ce fichier, je vais intégrer Bootstrap car il demande du CSS que du Javascript; pour l'exemple c'est donc parfait. Si on ajoute les liens dans le layout, ils seront accessibles à l'ensemble des fichiers Twig ayant pour base ce layout.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}

        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
        {% block javascripts %}{% endblock %}
    </body>
</html>

Vous remarquerez que j'ai intégré les CDN avant les blocs en question et non à l'intérieur. Pourquoi ? Car les blocs ont pour objectifs d'être remplacés par le contenu d'un fichier de vue. En les mettant avant, je pourrai les utiliser dans l'ensemble des mes vues.

Maintenant, je vais ajouter un lien Javascript que je souhaite avoir seulement dans un fichier de vue et non dans l'ensemble de l'application.

{# Template général de la page #}
{% extends 'base.html.twig' %}

{# Titre de la page #}
{% block title %}Mon titre{% endblock %}

{% block stylesheets %}
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.2/styles/default.min.css">
{% endblock %}

{# Ici vous mettez vos codes HTML et balises Twig au besoin #}
{% block body %}
    <h1>Mon super titre H1</h1>
    <p>Mon texte</p>
{% endblock %}

{% block javascripts %}
    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.2/highlight.min.js"></script>
{% endblock %}

À la différence du layout, j'ai inséré cette fois-ci les CDN dans les blocs correspondants. Faites attention au fait que les blocs soient bien séparés et non les uns dans les autres !

Au moment du chargement de cette vue, Twig intègrera les nouveaux CSS et Javascript dans la base, en dessous de ceux déjà présents, (ici ceux de Bootstrap), et les exécutera. Mais, au changement de page, ceux deux liens disparaîtront.

Note : l'ordre des blocs n'a aucune importante. ?

Afficher une variable

Petit rappel concernant les variables Twig: Dans le cadre de Symfony, nous les recevons d'une méthode contenue dans un contrôleur.

/**
 * @Route("/home", name="home")
 */
public function index()
{
    return $this->render('home/index.html.twig', [
        'firstname' => 'John',
    ]);
}

Le code ci-dessus retourne une variable nommée "firstname" dont la valeur est "John". Reste à afficher cette variable dans le fichier de vue et pour ça, rien de plus simple !

{{ firstname }}

Et voilà ! ?
Un exemple sera plus clair :

{# Template générale de la page #}
{% extends 'base.html.twig' %}

{# Titre de la page #}
{% block title %}Mon titre{% endblock %}

{# Ici vous mettez vos codes HTML et balises Twig au besoin #}
{% block body %}
    <p>Bonjour {{ firstname }}</p>
{% endblock %}

Le résultat sera "Bonjour John".

Les conditions

En plus d'afficher des simples variables, Twig permet d'appliquer des conditions avec un if.

{% if firstname == Pierre) %}
    <p>Vous êtes Pierre</p>
{% endif %}

Le même code avec un else cette fois-ci :

{% if firstname == 'Pierre' %}
    <p>Vous êtes Pierre</p>
{% else %}
    <p>Vous n'êtes pas Pierre !</p>
{% endif %}

Vous retrouverez les opérateurs de comparaisons habituels excepté le strictement égal === et le strictement différent !==. Ils existent, mais sont remplacés par une phrase :

  • === : is same as()
  • !== : is not same as()

Exemple :

{% if firstname is not same as('Pierre') %}
    <p>Vous n'êtes pas Pierre</p>
{% endif %}

Les boucles

Twig peut aussi afficher le contenu d'un tableau avec une boucle for, qui est l'exact équivalent à foreach en PHP, mais avec une petite subtilité.

{% for item in items %}
    <p>{{ item }}</p>
{% endfor %}

Elle fonctionne donc comme foreach mais à l'envers ?. Vous devez d'abord préciser la variable recevant l'information et ensuite le tableau à lire, séparé par un in et non un as. La lecture de la boucle peut d'ailleurs être comprise comme : "pour un élément dans les éléments".

Si jamais votre tableau "items" est vide, Twig ne provoquera aucune erreur, mais ne traitera pas la boucle for tout simplement. Dans un cas comme celui-ci, nous pouvons afficher un message à la place.

{% for item in items %}
    <p>{{ item }}</p>
{% else %}
    <p>Mon tableau est vide !</p>
{% endfor %}

Sympa non ?

Générer une URL

Si vous possédez plusieurs pages dans votre application, il est évident que vous souhaiterez créer des liens pour naviguer sur celle-ci.

En temps normal, vous créez un lien avec la balise a et le lien dans le href :

<a href="/home" title="Mon autre page">Lien</a>

Le souci vient à partir du moment où vous souhaitez modifier la route pour accéder à cette page. La méthode ci-dessus vous obligera à retrouver tous les liens dans toutes vos vues pour modifier le href. Perte de temps et pas très pratique. ?

Twig nous permet de générer un lien relatif en utilisant la fonction path() et en utilisant le nom de la route choisie au moment de sa création.

<!-- a href="/home" title="Mon autre page">Lien</a -->
<a href="{{ path('home') }}" title="Mon autre page">Lien</a>

Cette fonction est très pratique, car si vous souhaitez modifier la route, il suffit de modifier l'annotation @Route() et tous les liens pointant vers cette route seront mis à jour.

Pour générer un lien absolu, utilisez la méthode url().

<!-- a href="http://mon-domaine.com/home" title="Mon autre page">Lien</a -->
<a href="{{ url('home') }}" title="Mon autre page">Lien</a>

Si ma route comporte un paramètre ? Comment puis-je faire ? ?
Toujours en utilisant path() ou url(). En deuxième paramètre de cette fonction, passez un tableau JSON comme ceci :

<!-- a href="http://mon-domaine.com/home/3" title="Mon autre page">Lien</a -->
<a href="{{ url('home', { id: 3 }) }}" title="Mon autre page">Lien</a>

S'il y a plusieurs paramètres ?
Toujours de la même manière :

<!-- a href="http://mon-domaine.com/home/3/martine-a-la-plage" title="Mon autre page">Lien</a -->
<a href="{{ url('home', { id: 3, slug: 'martine-a-la-plage' }) }}" title="Mon autre page">Lien</a>

Insérer un fichier CSS, Javascript ou même une image

Tout comme pour la génération des liens entre les pages, l'insertion d'un asset se génère avec une fonction : asset(). Cette fonction va automatiquement aller chercher le fichier dans le dossier "public", là où tous vos "assets" doivent se situer et générer un lien relatif.

Si dans votre dossier "public", vous avez créé un dossier nommé "css" et à l'intérieur de celui-ci un fichier "styles.css", vous ferez l'insertion de cette façon :

<!-- link rel="stylesheet" href="public/css/styles.css" -->
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">

Pour une fichier Javascript ou une image, le procédé reste exactement identique !

Si vous voulez un lien absolue et non relatif, encerclez asset() de la fonction absolute_url().

<!-- link rel="stylesheet" href="http://mon-domaine.com/public/css/styles.css" -->
<link rel="stylesheet" href="{{ absolute_url(asset('css/styles.css')) }}">

Filtres Twig

Twig intègre un système de filtre à appliquer sur vos variables. Vous pourrez par exemple les couper (avec slice), tout mettre en majuscules (avec upper), arrondir au chiffre supérieur (avec round)...

Pour appliquer un filtre sur une variable, il faut la séparer par un pipe | et ensuite le nom du filtre que vous utilisez.

Pour mon exemple, j'ai une variable nommée text ayant pour valeur "tout va bien".

{# Affiche "Tout va bien" #}
{{ text|capitalize }}

{# Affiche "TOUT VA BIEN" #}
{{ text|upper }}

{# Affiche "tou" #}
{{ text|slice(3) }}

Il existe une multitudes de filtres proposés par Twig et on peut même en créer nous-même ! On regardera ça de plus près dans un autre article. ?

Tester les variables

En PHP, nous avons le var_dump() pour tester nos variables (quand on n’utilise pas les tests unitaires ou fonctionnels... ?)

Twig possède aussi sa fonction de test.

{{ dump(variable) }}

Rappel

Comme vous pouvez le voir, Twig reste relativement simple à apprendre et à mettre en place.

Voilà un petit rappel pour ne plus vous mélanger les pinceaux entre les doubles accolades ou accolades avec pourcentages...

  • double accolades {{ ... }} : affiche quelques chose
  • accolades avec pourcentages : {% ... %} : fais quelques chose
  • accolades avec dièses (ou hashtags ?) {# ... #} : rien, ce sont les commentaires Twig