Cet article est une synthèse de la documentation officielle :
Twig.
Twig est un gestionnaire de templates, des fichiers contenant du texte et des variables qui seront remplacées à terme,
lorsqu'on "rendra" le template, par des valeurs.
C'est un langage écrit en PHP, générant du PHP. Les templates seront transpilés en PHP.
La fonction la plus courante est le echo()
de PHP, représentée par {{ }}
dans Twig, pour afficher une valeur.
Comme pour les autres composants, on peut lister ses options de paramétrage disponibles via la commande :
bin/console config:dump twig
Blocs
Un bloc est une partie de template nommée et réutilisable. On peut le considérer comme une fonction
qui affiche du contenu.
Par défaut, les blocs sont affichés/exécutés/rendus dès qu'ils sont présents dans un template. On peut toutefois en
appeler un explicitement comme une fonction :
- s'il est dans le même contexte que le template courant :
- dans le même fichier
- dans un template parent (cf. héritage ci-après)
- dans un template "utilisé" (via le tag
use
), à la manière d'un Trait PHP
- ou si on indique le fichier dans lequel il se trouve explicitement
{# Bloc dans le même template, ou son parent #}
{{ block('my_block') }}
{# Blocs déclarés dans un autre template, mais considéré comme faisant partie du contexte #}
{% use 'a_template_with_several_blocks.twig' %}
{{ block('custom_block_1') }}
{{ block('custom_block_2') }}
{{ block('custom_block_3') }}
{# Bloc déclaré dans un autre template, lequel est indiqué explicitement #}
{{ block('my_external_block', 'another_template.twig') }}
Comme beaucoup de langage permettant de créer des fonctions dans d'autres fonctions, Twig permet de créer des blocs dans
des blocs.
Macros
Les blocs sont comme des fonctions, mais n'ont pas d'argument. Les variables disponibles sont celles du contexte.
Une macro est comme un bloc sauf :
- qu'elle peut recevoir des arguments
- elle n'est pas affichée/exécutée/rendue automatiquement (il faut l'appeler explicitement)
Exemple :
{% macro my_macro(text) %}
Some text: {{ text }}
{% endmacro %}
{# Appel de la macro présente dans le contexte #}
{{ _self.my_macro('some text') }}
{# Appel dune macro présente dans un autre template #}
{% import 'my_macros.twig' as my_alias %}
{{ my_alias.some_external_macro() }}
Héritage
L'héritage de templates fonctionne comme l'héritage de classes. Les blocs sont alors comme des méthodes publiques, dont
on hérite et que l'on peut surcharger.
Pour déclarer un template comme enfant d'un autre, on utilise le tag extends
(comme pour une classe PHP) :
{% extends 'base.html.twig' %}
{# Pour vider le contenu d'un bloc parent, on le redéclare en le laissant vide #}
{% block title %}
{% endblock %}
{# Pour remplacer le contenu d'un bloc parent, on le redéclare avec le nouveau contenu souhaité #}
{% block title %}
My child title
{% endblock %}
{# Pour conserver le contenu d'un bloc parent mais y ajouter du contenu #}
{% block title %}
My child title | {{ parent() }}
{% endblock %}
Remarque :
Spécificité Twig, un template enfant ne doit jamais afficher du contenu hors des blocs définis par son parent.
Pour expliquer cela, on peut considérer le parent comme une interface. Seuls ses blocs (= ses méthodes) sont connues.
Quand on manipule les templates (= les objets) qui l'implémentent, on n'a connaissance que de celles-ci.
Surcharge de blocs externes
À l'instar d'un trait PHP, un bloc de template externe inclus au contexte (via le tag use
) peut être surchargé.
On peut alors vider, remplacer, ou modifier le contenu du bloc (toujours via la fonction parent()
) .
Variables globales
Une variable globale est disponible partout dans Twig, à tout moment. La liste de ces variables est visible
avec la commande suivante :
bin/console debug:twig
Remarque : ces variables peuvent être surchargées par une variable locale au template (si elles ont le même nom).
Variables internes
Twig en fournit 3 par défaut :
_self
: le nom du template courant
_context
: la liste des variables (locales) disponibles (celles passées dans le render()
côté PHP)
_charset
: le charset courant
Variables globales Symfony
Symfony (ou plutôt son Twig Bridge) en ajoute une pour nous : app
(documentation).
Ajouter des variables globales
On peut en ajouter
- directement via un fichier de config :
# config/packages/twig.yaml
twig:
globals:
my_var: 'value'
my_param1: '%some_parameter%'
my_service: '@my_service'
my_array_var:
- 'Monday'
- 'Tuesday'
- dans une extension Twig, qui doit alors implémenter
GlobalInterface
et sa méthode getGlobals()
.
Inclusions
Pour inclure un template dans un autre, on utilise le tag include
, ou la fonction include()
:
{# Avec tout le contexte courant #}
{% include 'template.twig' %}
{{ include('template.twig') }}
{# Avec le contexte courant et des variables spécifiques #}
{% include 'template.twig' with {'my_var': 'some_value'} %}
{{ include('template.twig', {'my_var': 'some_value'}) }}
{# Sans le contexte courant #}
{% include 'template.twig' only %}
{{ include('template.twig', with_context = false) }}
{# Sans le contexte courant mais des variables spécifiques #}
{% include 'template.twig' with {'my_var': 'some_value'} only %}
{{ include('template.twig', {'my_var': 'some_value'}, with_context = false) }}
Il est considéré comme une bonne pratique de retirer le contexte courant lors de l'inclusion
L'avantage d'utiliser le tag, est de pouvoir facilement basculer vers embed
si on a besoin de plus de flexibilité
(cf. ci-après).
Inclusion avec surcharge
Pour modifier l'un des blocs du contenu inclus, il faut utiliser le tag embed
.
{# Avec tout le contexte courant #}
{% embed 'template.twig' %}
{% block my_block %}
This block has been overrided!
{% endblock %}
{% endembed %}
On peut retirer le contexte pour embed
de la même manière que include
.
Filtres et fonctions
La distinction entre les deux est la même que dans le monde bash Linux.
Un filtre est une fonction, recevant au moins un argument.
{{ some_function() }}
{{ some_function_with_args('arg1', 'arg2') }}
{{ 'a string as argument'|some_filter }}
{{ 'a string as argument'|some_filter_with_more_args('arg2') }}
Les filtres permettent de chainer facilement plusieurs traitements sans que la variable initiale soit perdue dans la
notation :
{# avec filtres, on lit de gauche à droite #}
{{ 'mon texte'|gras|italique|color('red')|majuscule }}
{# avec fonctions, on lit de l'intérieur vers l'extérieur #}
{{ majuscule(color(italique(gras('mon texte)), 'red')) }}
Opérateurs logiques
Ces opérateurs ajoutent du sucre syntaxique pour les if
des templates.
Exemples (in
, is
, matches
, starts
, end
) :
{% if 'monday' in my_day_list %}{% endif %}
{% if 'a' in my_string %}{% endif %}
{% if my_var is defined %}{% endif %}
{% if my_var matches '/some_regex/' %}{% endif %}
{% if my_var starts with 'my_prefix' %}{% endif %}
{% if my_var not ends with 'my_suffix' %}{% endif %}
Il est possible d'en ajouter dans une extension twig, en implémentant la méthode getOperators()
.
Tests
Les tests ajoute du sucre syntaxique pour les if
des templates contenant l'opérateur logique is
.
Exemples (defined
, empty
, iterable
, odd
) :
{% if my_var is defined %}{% endif %}
{% if my_var is empty %}{% endif %}
{% if my_var is iterable %}{% endif %}
{% if my_var is odd %}{% endif %}
{% if my_var is sameas(some_var) %}{% endif %}
Il est possible d'en ajouter dans une extension twig, en implémentant la méthode getTests()
.
Extensions Twig
Documentation
Une extension Twig permet d'ajouter des filtres et fonctions personnalisés, mais également des tags,
des opérateurs logiques, des tests, ...
Ajout de filtres et fonctions
Les filtres (TwigFilter
) et fonctions (TwigFunction
) que l'on déclare sont des objets qui associent un nom à un
Callable. Lorsqu'on utilisera ce nom dans un template, le Callable sera appelé.
Remarque : par défaut, toutes fonctions ou filtres ajoutés verra sa valeur de retour échappée.
Pour éviter cela, on peut indiquer ['is_safe' => ['html']]
par exemple lors de leur déclaration.
Dépendances dans une extension
Si une extension a besoin d'un service pour le traitement de l'un de ses filtres ou fonctions, il faut éviter d'ajouter
cette dépendance à l'extension, par soucis de performance.
En effet, si on l'injecte dans le constructeur de l'extension, il sera instancié très tôt durant l'exécution :
dès l'enregistrement de l'extension.
Pour éviter ça, on peut déplacer le code nécessitant la dépendance dans un service dédié,
implémentant TwigE\Extension\RuntimeExtensionInterface
.
On utilisera ensuite le nom de ce service comme "instance" de Callable, et twig l'instanciera automatiquement
au bon moment :
use \Twig\TwigFilter;
use \App\Twig\MyServiceRuntime;
public function getFilters(): array {
return [
new TwigFilter('my_filter', [MyServiceRuntime::class, 'methodOfServiceToCall']),
];
}
Le service ne sera instancié qu'à l'appel du filtre ou de la fonction, et pas avant.
Il est donc logique de créer un service par dépendance.
La convention utilisée par les extensions du Twig Bridge est de les ranger sous le namespace \App\Twig
avec les extensions, et de les suffixer avec Runtime
.
Échappement
L'échappement permet à du texte de ne pas être interprêté par un langage.
Dans Twig, par défaut toute variable affichée (ex: {{ my_variable }}
) est échappée.
Échappement par défaut
Toutes ces syntaxes sont équivalentes :
{{ my_var }}
{{ my_var|e }}
{{ my_var|escape }}
{{ my_var|escape('html') }}
Explication : e
est un alias au filtre escape
, qui reçoit par défaut la valeur html
.
Type d'échappement
Le filtre escape
accepte les valeurs suivantes en argument : html
, js
, css
, url
, html_attr
.
Le dernier permet d'échapper les attributs des balises HTML.
Annuler l'échappement
Le filtre raw
permet d'annuler l'échappement de la valeur.
{{ my_var|raw }}
Une autre technique consiste à changer l'extension du template twig.
Si votre template s'appelle my_template.js.twig
, alors escape
recevra js
pour valeur.
Si toutefois le filtre ne reconnais pas l'extension, html
sera choisi.
Rendu de contrôleur
Imaginons qu'on souhaite afficher le nom de l'utilisateur courant en haut de chaque page de l'application.
Tous nos templates héritent d'un même parent qui contient un bloc affichant cette variable.
Pour que l'utilisateur courant soit passé au template, la solution de base consiste à l'ajouter dans le
tableau des variables lors du render()
. Cela implique par contre de devoir la passer pour les rendus de tous les
templates enfants et vient complexifier le contrôleur qui demande le rendu.
La solution consiste à créer un contrôleur dédié au rendu de cette variable uniquement, qui sera lui-même
directement rendu dans le template :
{{ render(controller('App\\Controller\\MyController::myAction', {})) }}
Explications :
- La fonction
render()
appelle une URL et en affiche la réponse
- La fonction
controller()
retourne la réponse du contrôleur en argument
Utiliser le rendu de contrôleur est considéré comme une bonne pratique.
On peut la privilégier pour chaque bloc du design mutualisé entre plusieurs pages.
Http cache
L'inclusion de contrôleur permet de bénéficier du Cache HTTP via les fragments.
Pour cela, il faut remplacer render()
par render_esi()
, et activer son utilisation dans le framework.yaml
.
Traduction
Pour traduire une chaîne, on peut utiliser le filtre trans
ou le bloc {% trans %}{% endtrans %}
.
Le premier échappe la chaîne.
Traductions conditionnelles
Pour faire varier la traduction selon une variable (ex: le nombre ou le genre), on peut utiliser ICU.
Pour que le service de traduction utilise bien cette bibliothèque système, il faut ajouter le suffixe
+intl-icu
au nom du fichier de traduction (ex: messages+intl-icu.fr_FR.yaml
).
Documentation
Autres
Filtre de tableau
Depuis PH7.4 et l'arrivée des arrow functions, le filtre filter
accepte des fonctions anonymes :
{% for item in items|filter(i => i.relevent) %}
<p>{{ item.name }}</p>
{% else %}
<p>No item</p>
{% endfor %}
sprintf()
Le filtre format()
disponible dans les templates équivaut au sprintf()
de PHP.
{{ 'My string with %d word(s).'|format(5) }}
dump()
La fonction dump()
comme dans PHP affiche le contenu d'une variable (ou de toutes celles disponibles
si on l'appelle sans argument).
Pour les collecter mais ne pas les afficher dans le contexte (et seulement les voir via la debugtoolbar),
on peut utiliser le tag dump
à la place (ie. {% dump my_var %}
)
Déconnexion
La fonction logout_path()
a été ajoutée pour générer une URL de logout directement.
Il faut ajouter la route dans le security.yaml
.
Formatage
Beaucoup de fonctions ont été ajoutées pour formater une heure, une date, un nombre, ...
Elles sont toutes préfixées par format_
.
Elles sont fournies par IntlExtension
.