Twig est un moteur de template ( gabarit ) utilisé nativement par Symfony.
Le twig permet d'écrire de façon rapide ce dont nous avons besoin. Par exemple:
<!DOCTYPE html>
<html>
<head>
<title>Salut Symfony-Nautes!</title>
</head>
<body>
<h1>{{ titre }}</h1>
{% if user.isLoggedIn %}
Salut {{ user.name }}!
{% endif %}
{# ... #}
</body>
</html>
La lecture est plutôt simple,
{{ ... }}
est utilisé pour afficher le contenu d'une variable.{% ... %}
est utilisé pour effectuer une action logique comme une condition ou une boucle.{# ... #}
correspond à des commentaires qui ne seront pas rendus à l'affichage.On peut également utiliser et créer de filtres ( pipe ) comme par exemple {{ titre|upper }}
qui mettra le titre en majuscule.
Comme nous l'avons vu précédemment les templates se trouvent dans le dossier /templates
et sont appelés par les contrôleurs avec la méthode render()
Comme ici:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
#[Route('/home', name: 'app_home')]
public function index(): Response
{
// ICI <=======
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}
}
Cette méthode va chercher le template et lui envoie les données contenues dans le tableau associatif.
Côté template, Twig va récupérer les données du tableau par clés. Par exemple, si on envoie le tableau suivant:
[
'user' => $user,
'tab' => ['truc' => 234 , 'machin' => 45]
]
on peut écrire dans Twig
<p> {{ user.name }} a mangé {{ tab.machin }} pommes </p>
En réalité Twig, avec la notation truc.machin
va aller chercher la variable en utilisant ces différentes méthodes dans l'ordre:
$truc['machin']
$truc->machin
$truc->machin()
$truc->getMachin()
$truc->isMachin()
$truc->hasMachin()
Et s'il ne trouve pas il renvoie null
.
Si vous vous souvenez, nous avons défini un nom dans la route du contrôleur
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
#[Route('/home', name: 'app_home')] // ICI <===
public function index(): Response
{
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}
}
Plutôt que d'écrire l'url de la route dans nos lien au sein du Twig, nous allons utiliser la fonction path()
qui génèrera l'url pour nous:
<a href="{{ path('app_home') }}">Accueil</a>
Il existe également une fonction pour accéder aux assets (image, css, js ...) que vous aurez déposés dans le dossier public
.
Par exemple:
{# ici l'image se trouve dans le dossier "public/images/logo.png" #}
<img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>
Je ne vais citer ici que quelques exemples mais je vous invite à consulter la doc de Twig pour plus de détails.
D'abord les boucles, il est très simple de boucler dans twig. Si nous revenons à l'exemple précédent. J'avais envoyé un tableau tab
dans ma vue. Je peux énumérer le tableau en utilisant simplement:
{% for mot in tab %}
<p>{{mot}}</p>
{% endfor %}
De même, les conditions existent aussi.
{% for mot in tab %}
<p>{% if mot > 100 %}
gros
{% else %}
petit
{% endif %}
</p>
{% endfor %}
Un des principe du moteur de template est de ne pas réécrire les choses. Par exemple, le gabarit principal de l'application.
Disons que nous allons utiliser Bootstrap. Je vais définir les imports une fois pour toute dans base.html.twig
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% block title %}4WDev, développement tout terrain!
{% endblock %}
</title>
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
<!-- JavaScript Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
</body>
</html>
Vous voyez déjà que des notations {% block ... %} ... {% endblock %}
sont présentes. Ces block sont des emplacements que nous pourrons remplir ou laisser tel quel dans les vue qui hériterons de cette base.
Ainsi dans la vue templates/home/index.html
nous avons:
{% extends "base.html.twig" %} {# <== ici nous héritons de base #}
{% block body %}
<h1>Accueil</h1> {# <== ici on rempli le block body #}
{% endblock %}
Cette vue hérite de base, c'est à dire qu'elle reprend tout le gabarit de base avec ses blocks. Comme nous ne surchargeons pas le block title
le titre de la page reste "4WDev, développement tout terrain!". Par contre le block body est rempli avec <h1>Accueil</h1>
. L'inclusion de boostrap est toujours active.
Une des forces de Twig est de pouvoir inclure une vue dans une autre. Par exemple, vous pouvez définir la barre de navigation une fois pour toute et la réutiliser la où vous en avez besoin.
Dans un fichier templates/partials/header.html.twig
créons la barre de navigation (c'est la navbar de bootstrap par défaut).
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
Puis nous n'avons qu'à l'inclure dans base.html.twig
ou une autre.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% block title %}4WDev, développement tout terrain!
{% endblock %}
</title>
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
{% include "partials/header.html.twig" %} {# ICI <== inclusion de la navbar #}
{% block body %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
</body>
</html>
Nous avons fait le tour pour le moment. Je reviendrai plus tard sur le twig pour définir des filtres personnalisés.