I. À propos▲
Twig est un moteur de templates qui a été créé par SensioLabs, les créateurs de Symfony.
On le retrouve nativement dans les frameworks Symfony et Drupal8, mais il peut être installé sur la majorité des frameworks ainsi que dans un environnement PHP.
Dans ce tutoriel pas de long discours explicatif. J'ai choisi de présenter un exemple Twig et son équivalent en PHP. Parfois aucune explication, en lisant l'exemple du code, on comprend le fonctionnement.
Même si ce tutoriel est orienté Symfony, il peut être valable pour apprendre Twig de façon générale.
II. Avantages de Twig▲
- Twig permet de séparer la présentation des données du traitement.
- Twig permet la personnalisation de page web.
- Twig permet de rendre les pages web plus lisibles, plus claires.
- Twig est rapide.
- Twig apporte de nouvelles fonctionnalités.
- Twig apporte plus de sécurité.
Mais aussi :
- Twig est facile à apprendre ;
- les messages d'erreur Twig sont clairs et précis ;
- Twig est supporté par la plupart des éditeurs de code.
A.
Bien souvent, afficher une donnée brute ne suffit pas. On veut lui appliquer un traitement logique avant de l'afficher (une conversion, un calcul…).
Twig permet de définir en dehors de la page web des filtres que l'on pourra appliquer à la donnée.
B.
Un block menu, un block recherche, un block contenu… le tout défini dans un template lui-même héritable.
C.
On a souvent besoin de boucles, de conditions… à la construction d'une page web et pour cela, on utilise PHP. Votre page devient vite illisible, peu maintenable, sujette à de nombreuses erreurs.
Twig par son langage est moins invasif que PHP et se substitue à celui-ci.
TWIG Sélectionnez
|
PHP Sélectionnez
|
D.
La conversion Twig/PHP ne se fait qu'une seule fois au premier appel, ensuite il est mis en cache pour les autres appels (et donc finalement, c'est aussi rapide qu'en PHP).
E.
Twig apporte de nouvelles fonctionnalités: les macros, les filtres, les blocks, l'héritage de templates…
F.
Avec Twig, plus de sécurité avec l'échappement des variables qui est activé par défaut.
III. Le langage Twig▲
Faisons le tour de toute la syntaxe du langage Twig.
{% %}: pour faire une action : un set de variable, une condition if, une boucle for ;
{{ }}: pour afficher quelque chose.
III-A. Affichage des données▲
L'affichage des données et l'échappement :
TWIG Sélectionnez
|
coucou |
TWIG Sélectionnez
|
<h1>coucou</h1> |
Pour la sécurité, l'échappement est activé par défaut. Les balises ne sont pas interprétées. L'échappement convertit la donnée en code HTML avant l'affichage.
Forcer l'interprétation des balises avec le filtre raw :
TWIG Sélectionnez
|
coucou |
Si l'échappement par défaut a été désactivé. Forcer l'échappement avec le filtre e :
TWIG Sélectionnez
|
<h1>coucou</h1> |
L'affichage des données de différents types: objet, tableau…
TWIG Sélectionnez
|
TWIG Sélectionnez
|
PHP Sélectionnez
|
TWIG Sélectionnez
|
L'affichage de données avec suppression d'espace entre deux balises à gauche ou à droite ou les deux :
supprimer les espaces entre les balises HTML
TWIG Sélectionnez
|
Conversion en : (avant affichage) Sélectionnez
|
supprimer les espaces à gauche puis des deux côtés avec le caractère '-' :
TWIG Sélectionnez
|
Conversion en: (avant affichage) Sélectionnez
|
III-B. Concaténation▲
TWIG Sélectionnez
|
PHP Sélectionnez
|
TWIG Sélectionnez
|
PHP Sélectionnez
|
TWIG Sélectionnez
|
PHP Sélectionnez
|
III-C. Les variables▲
{%
set
pi =
3
.14
%}
{%
set
foo =
'foo'
%}
- tableau simple avec une série de valeurs:
{%
set
tableau=[
1
,
2
,
3
]
%}
- tableau avec des clés:
{%
set
tableau={
key1:
value1,
key2:
value2}
%}
- tableau avec valeur et clé:
{%
set
foo =
[
3
,
{
"mot"
:
"soleil"
}]
%}
- afficher le contenu de 'mot' donc 'soleil':
{{
foo[
1
][
'mot'
]
}}
- déclarer 2 variables en même temps // foo='foo' // bar='bar':
{%
set
foo,
bar =
'foo'
,
'bar'
%}
- foo contient le texte entre les 2 balises:
{%
set
foo %}
<div
id
=
"pagination"
>
…
</div>
{%
endset
%}
[ … ] un tableau avec une série de valeurs.
{ … } un tableau avec des couples clé/valeur.
Les variables globales (Symfony)
parameters,yml Sélectionnez
|
Config.yml Sélectionnez
|
TWIG Sélectionnez
|
Résultat à l'affichage Sélectionnez
|
La variable globale webmaster sera accessible dans tous les templates Twig.
Cas particulier : une variable nommée avec un caractère spécial. ('-'…). L'attribut d'un objet (article.info-produit) ou la clé d'un tableau article['info-produit'] :
une donnée avec '-' Sélectionnez
|
TWIG Sélectionnez
ERREUR -> {{ article.data-description }} |
une donnée avec '-' Sélectionnez
|
TWIG Sélectionnez
ERREUR -> {{ tableau.data-info }} |
à savoir :
{{
attribute(
object,
method)
}}
{{
attribute(
object,
method,
arguments)
}}
{{
attribute(
array,
item)
}}
{{
attribute(
object,
method)
is
defined?
'Method exists'
:
'Method does not exist'
}}
III-D. Condition IF : {% if … %} … {% endif %}▲
and, or, not, is defined, starts with, ends with, matches, in, empty
{%
if
produits is
empty %}
il n'y a plus de produit
{%
endif
%}
{%
if
((
a==
1
and
b>
0
)
or
not
c==
0
)
and
d is
defined %}
{%
set
resultat =
(
d +
a *
b)
/
c %}
{{
resultat }}
{%
endif
%}
{%
if
'Fabien'
starts with
'F'
%}
commence par F
{%
endif
%}
{%
if
'Fabien'
ends with
'n'
%}
Finis par n
{%
endif
%}
- regular expressions:
{%
if
phone matches '/^[
\\
d
\\
.]+$/'
%}
format telephone ok
{%
endif
%}
{%
if
5
not
in
[
1
,
2
,
3
]
%}
5 non présent
{%
endif
%}
mais aussi :
is null: si est null ;
is constant: comparer si est une constante ;
is divisible by(x): si est divisible par x ;
is even: si est pair ;
is odd: si est impair ;
is iterable: si est du type itérable (comme une liste) ;
is same as: comparer 2 variables (en php correspond ===).
Sous une autre forme :
{{
article is
null?
'yes'
:
'no'
}}
// affiche yes ou no
{{
var is
odd }}
// Affiche true ou false
// pareil que: {%
if
var is
odd %}
true{%
else
%}
false{%
endif
%}
{{
var is
null }}
// Affiche true ou false
III-E. La boucle FOR: {% for … %} {% endfor %}▲
- affiche 0123456789
{%
for
i in
0
..9
%}
// pareil que {%
for
i in
range(
0
,
9
)
%}
{{
i }}
{%
endfor
%}
{%
for
produit in
produits %}
{{
produit.nom }}
{%
endfor
%}
- avec une condition:
{%
for
produit in
produits if
produit.etat==
1
%}
{{
produit.nom }}
{%
endfor
%}
- boucle for avec condition vide:
{%
for
article in
articles %}
{{
article.nom }}
{%
else
%}
pas d'article trouvé
{%
endfor
%}
- clés et valeurs:
{%
for
key,
value in
table %}
{{
key }}
{{
value }}
{%
endfor
%}
- les 10 premiers users:
{%
for
user in
users|
slice(
0
,
9
)
%}
<li>
{{
user.username }}
</li>
{%
endfor
%}
- bascule:
{%
for
i in
0
..10
%}
<div
class
=
"
{{
cycle([
'fond-noir'
,
'fond-blanc'
],
i)
}}
"
>
// fond-noir / fond-blanc / …
</div>
{%
endfor
%}
La variable loop de la boucle for :
{{
loop.index }}
// Numéro de l'itération courante en commençant par 1
{{
loop.index0 }}
// Numéro de l'itération courante en commençant par 0
{{
loop.revindex }}
// Nombre itérations restantes avant la fin en commençant par 1
{{
loop.revindex0 }}
// Nombre itérations restantes avant la fin en commençant par 0
{{
loop.first }}
// La première itération? True ou false
{{
loop.last }}
// La dernière itération? True ou false
{{
loop.length }}
// Nombre total d'itérations
Un exemple avec loop :
{%
for
produit in
produits %}
<span
class
=
"
{%
if
loop.index is
even %}
fond-noir
{%
else
%}
fond-blanc
{%
endif
%}
"
>
{{
produit }}
</span>
{%
endfor
%}
III-F. Commentaire: {# … #}▲
{# mon commentaire #}
III-G. Les filtres▲
On peut appliquer des filtres sur une variable à afficher, sur une variable d'une condition IF ou d'une boucle FOR…
Le filtre length
TWIG Sélectionnez
|
PHP Sélectionnez
|
Appliquer plusieurs filtres: trim et upper
TWIG Sélectionnez
|
PHP Sélectionnez
|
Une autre façon d'appliquer un filtre upper
TWIG Sélectionnez
Avec un long texte, sous cette forme c'est plus pratique et plus clair. |
à savoir:
La liste des filtres
http://twig.sensiolabs.org/doc/filters/index.html
abs, batch, capitalize, convert_encoding, date, date_modify, default, escape, first, format, join, json_encode, keys, last, length, lower, merge,
nl2br, number_format, raw, replace, reverse, round, slice, sort, split, striptags, title, trim, upper, url_encode
Nous verrons plus loin comment créer nos propres filtres.
III-H. Les fonctions▲
Une fonction effectue un traitement (contrairement à un filtre qui est destiné à faire une conversion) :
- la fonction dump: affiche tout le détail d'un objet ou d'un tableau
{{
dump(
produit)
}}
- la fonction max: retourne le max d'une série de valeurs
{%
set
tab =
[
1
,
5
,
2
,
3
]
%}
{{
max(
tab)
}}
// affiche 5
Liste des fonctions :
attribute, block, constant, cycle, date, dump, include, max, min, parent, random, range, source, template_from_string
III-I. Les macros▲
Les macros sont comme des fonctions, mais composées de fragments HTML réutilisables.
Dans un template, on regroupe toutes les macros que l'on pourra importer dans un autre template afin d'être utilisées.
Prenons pour exemple un template forms.html.twig qui va contenir plusieurs macros. Une macro pour la balise input et une qui affiche une liste d'article.
{%
macro
input(
name,
value,
type,
size)
%}
<input
type
=
"
{{
type|
default(
'text'
)
}}
"
name
=
"
{{
name }}
"
value
=
"
{{
value|
e }}
"
size
=
"
{{
size|
default(
20
)
}}
"
/>
{%
endmacro
%}
{%
macro
articleListe(
articles,
className)
%}
<ul {% if className %}
class
=
"
{{
className }}
"
{% endif %}>
{%
for
article in
articles %}
<li
class
=
"article"
><a
href
=
"
{{
article.url }}
"
>
{{
article.nom }}
</a></li>
{%
endfor
%}
</ul>
{%
endmacro
%}
Son utilisation : soit on importe toutes les macros, soit on choisit celle que l'on veut importer :
mapage.html.twig : avec import de toutes les macros se trouvant dans forms.html.twig Sélectionnez
|
mapage.html.twig : avec import d'une seule macro parmi les macros se trouvant dans forms.html.twig Sélectionnez
|
{%
macro
input(
name,
value,
type,
size)
%}
<input
type
=
"
{{
type|
default(
'text'
)
}}
"
name
=
"
{{
name }}
"
value
=
"
{{
value|
e }}
"
size
=
"
{{
size|
default(
20
)
}}
"
/>
{%
endmacro
%}
<p>
{{
_self.input(
'livre'
,
'saint-exupery'
,
'text'
,
64
)
}}
</p>
Une macro dans la page même de son utilisation, pour cela on utilise _self. Mais on perd sa réutisabilité.
III-J. Named Arguments▲
Donner des noms aux arguments pour une meilleure compréhension et de pouvoir cibler un argument.
Des exemples :
- au lieu de ... range(1, 10, 2) :
{%
for
i in
range(
low=
1
,
high=
10
,
step=
2
)
%}
{{
i }}
,
{%
endfor
%}
- l'argument ciblé est timezone parmi les autres arguments (format, date...)
{{
"now"
|
date(
timezone=
"Europe/Paris"
)
}}
//
{{
data|
convert_encoding(
from
=
'iso-2022-jp'
,
to=
'UTF-8'
)
}}
III-K. Les tags ou actions▲
{% … %} Les commandes de base Twig pour faire des actions comme: for, if, set…
Liste de toutes les commandes :
autoescape, block, do, embed, extends, filter, flush, for, from, if, import, include, macro, sandbox, set, spaceless, use, verbatim
III-L. Include de template (Symfony)▲
Inclure des fichiers template.
mapage.html.twig Sélectionnez
only est facultatif. Il permet d'indiquer que le fichier inclus n'a pas besoin des variables extérieures, mais seulement ceux passés en paramètres. |
Mais aussi :
- Un include avec une condition:
{%
include
isValid ?
'option1.html.twig'
:
'option2.html,twig'
%}
- Si la page de l'include n'existe pas, alors il n'y aura pas d'erreur. Rien ne se passe.
{%
include
"index.html"
ignore missing %}
III-M. Render controller (Symfony)▲
Faire appel à une action du contrôleur qui renvoie un rendu qui va être inclus.
Exemple : récupérer les trois derniers articles les plus récents :
{{
render(
controller(
'AcmeDemoBundle:Article:recentArticles'
,
{
'max'
:
3
}))
}}
// Fait appel à une action (recentArticlesAction) du controller (ArticleController)
// retourne un rendu avec les 3 derniers articles.
III-N. Personnaliser les templates: héritage, block…▲
Pour personnaliser les templates, Twig propose un système de blocks et d'héritage de templates.
Prenons cet exemple :
toutes les pages "client" et les pages "produit" ont le même header et footer ;
les pages client ont une recherche par client ;
les pages produit ont une recherche par produit ;
toutes les pages "client" et "produit" ont leur propre contenu respectif.
<html>
<head>
...</head>
<body>
<div
class
=
"head"
>
{%
block
header %}
header {%
endblock
%}
</div>
<div
class
=
"sidebar"
>
{%
block
recherche %}{%
endblock
%}
</div>
<div
class
=
"content"
>
{%
block
contenu %}{%
endblock
%}
</div>
<div
class
=
"footer"
>
{%
block
footer %}
footer {%
endblock
%}
</div>
</body>
</html>
Layout-client.html.twig Sélectionnez
|
Layout-produit.html.twig Sélectionnez
|
* Toutes les pages web 'client' Sélectionnez
|
* Toutes les pages web 'produit' Sélectionnez
|
Résultat |
|
Avec une page web "client" |
Avec une page web "produit" |
header |
header |
Les avantages :
- finalement, pour créer une page web "client" ou "produit", on hérite de son layout et on ajoute son propre contenu et… c'est tout ;
- si vous voulez modifier le header (de toutes les pages), il suffit de ne le modifier qu'à un seul endroit c'est-à-dire dans la base.
D'autres possibilités avec les blocks :
Identique |
TWIG Sélectionnez
Le fait de ne pas mentionner un block parent a pour résultat que le contenu est tout de même inclus automatiquement par héritage. |
header |
Identique |
TWIG Sélectionnez
{{ parent() }}: appelle du contenu parent |
header |
Supprimer un block parent |
TWIG Sélectionnez
Le fait de mentionner un block et qu'il soit vide a pour résultat que le contenu parent n'est pas appelé |
header |
Remplacer le contenu d'un block par un autre |
TWIG Sélectionnez
|
header |
Ajouter du contenu dans un block |
TWIG Sélectionnez
|
header |
Pour information, dans cette partie "personnalisation, héritage…" seule la façon d'écrire l'extends est liée à Symfony: {% extends "AcmeDemoBundle::layout-client.html.twig" %}.
III-O. Les liens (Symfony)▲
- vers fichiers assets :
<img
src
=
"
{{
asset(
'images/logo.png'
)
}}
"
/>
- Vers url / :
<a
href
=
"
{{
path(
'_welcome'
)
}}
"
>
Home</a>
- Vers url / avec des paramètres :
<a
href
=
"
{{
path(
'article_show'
,
{
'slug'
:
article.slug })
}}
"
>
{{
article.title }}
</a>
- Vers url absolue :
<a
href
=
"
{{
url(
'_welcome'
)
}}
"
>
Home</a>
III-P. L'inclusion de feuilles de style et de JavaScripts avec Twig (Symfony)▲
III-Q. Vérification de la syntaxe▲
Avec les commandes Symfony, on peut vérifier la syntaxe Twig à plusieurs niveaux: template, dossier, bundle.
- Par template :
php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig
- Par dossier :
php app/console twig:lint src/Acme/ArticleBundle/Resources/views
- Par bundle :
php app/console twig:lint @AcmeArticleBundle
III-R. Les formats de template (Symfony)▲
Les formats de template permettent de rendre en HTML, mais aussi en XML ou PDF.
http://symfony.com/doc/current/book/templating.html#including-stylesheets-and-javascripts-in-twig
III-S. créer un filtre (Symfony)▲
On a vu comment utiliser les filtres: {{ articles|length }}.
Il est possible de créer ses filtres, voici comment faire avec cet exemple.
Création d'un filtre price qui va formater une valeur en prix (monnaie dollars) :
src/AppBundle/Twig/AppExtension.php Sélectionnez
|
Déclarer la classe en tant que service :
services.yml Sélectionnez
|
Et plus qu'à utiliser le filtre price dans un template Twig :
TWIG Sélectionnez
|
$5500,25 |
TWIG Sélectionnez
|
$5,500.00 |
III-T. Math▲
Un peu de math pour la route
+ - * / : les bases
% : modulo donne le reste de la division
{{
11
%
7
}}
retourne 4.
// : arrondi inférieur ou supérieur sans les décimales
{{
20
//
7
}}
retourne 2
{{
20
//
6
}}
retourne 3
** : puissance
{{
2
**
3
}}
retourne 8
III-U. Coding Standards▲
Toujours un et un seul espace entre les balises, les données… (sauf pour les filtres) :
{{
foo }}
{# comment #}
{%
if
foo %}{%
endif
%}
{{
1
+
2
}}
{{
foo ~
bar }}
{{
true
?
true
:
false
}}
un seul espace après (mais pas avant) un séparateur :
{{
[
1
,
2
,
3
]
}}
{{
{
'foo'
:
'bar'
}
}}
inversement un seul espace avant (mais pas après) une parenthèse :
{{
1
+
(
2
*
3
)
}}
pas d'espace pour les filtres :
{{
foo|
upper|
lower }}
indenter les blocks :
{%
block
foo %}
{%
if
true
%}
true
{%
endif
%}
{%
endblock
%}
IV. Remerciements▲
Je tiens à remercier Francis Walter pour sa relecture technique, Claude Leloup pour sa relecture orthographique et Guillaume SIGUI pour son encadrement.