Créer une fonction Twig personnalisée sur Symfony 4

publié le 22/08/2018

Symfony 4 Twig

Il peut arriver que les fonctions Twig de base ne suffisent pas à réaliser ce que vous souhaitez dans vos templates. Heureusement, Twig est très facilement personnalisable et il s'avère très simple de créer sa propre fonction pour réaliser le besoin client.


Installation du projet

Pour installer Symfony 4, il y a 2 options principales:

  • installer la version lourde en utilisant le website-skeleton (conçue pour des sites internets classiques). Ce squelette correspond en gros à ce qu'on avait lorsqu'on installait un symfony 3 auparavant.
  • installer la version light, avec juste le coeur de Symfony. C'est l'option que nous allons retenir. Je l'aime beaucoup parce que cela rend le projet à la fois léger et modulable. En effet, grâce au nouveau composant Flex de Symfony, rajouter des dépendances est un vrai jeu d'enfant. Je vais vous montrer.

En ligne de commande:

composer create-project symfony/skeleton twig-fun-tuto
cd twig-fun-tuto

Installation des dépendances

Pour mener à bien ce projet, nous avons besoin de Twig évidemment, et des annotations pour configurer une route.
Pour faciliter le dev, nous allons ajouter le composant server qui est bien pratique à utiliser. Grâce à Flex, il suffit de faire:

composer require twig annotations
composer require server --dev

Dans ces quelques commandes réside la puissance de Flex, qui permet d'installer des dépendances très facilement.

bin/console server:start

And that's all! Nous voilà prêt à attaquer le coeur de notre sujet: la création de fonctions Twig personnalisées.


Objectif de cet article

L'objectif de cet article est de vous apprendre à créer une fonction Twig personnalisée. On va partir d'un exemple utile en rédigeant une fonction que j'ai régulièrement besoin dans mes projets. En effet, il m'arrive de vouloir savoir si une image existe bel et bien. Si l'image existe, je veux l'afficher évidemment. Mais si elle n'existe pas, je veux afficher une image par défaut. Au moyen de PHP, nous utiliserions la fonction file_exists mais Twig n'est pas livré avec une telle fonction. A nous de jouer.


Création de l'extension Twig

<?php
// src/Twig/AppExtension.php
namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class AppExtension extends AbstractExtension
{

    private $kernelProjectDir;

    // le constructeur indique que nous avons ici besoin d'un paramètre, le kernelProjectDir.
    // Nous allons passer ce paramètre en le déclarant dans notre fichier config/services.yaml. 
    // Il s'agit d'un paramètre par défaut introduit dans Symfony 4.
    public function __construct(string $kernelProjectDir)
    {
        $this->kernelProjectDir = $kernelProjectDir;
    }
    

    public function getFunctions()
    {
        return array(
            // on déclare notre fonction.
            // Le 1er paramètre est le nom de la fonction utilisée dans notre template
            // le 2ème est un tableau dont le 1er élément représente la classe où trouver la fonction associée (en l'occurence $this, c'est à dire cette classe puisque notre fonction est déclarée un peu plus bas). Et le 2ème élément du tableau est le nom de la fonction associée qui sera appelée lorsque nous l'utiliserons dans notre template.
            new TwigFunction('assetExists', array($this, 'assetExistsFunction')),
        );
    }

    // chemin relatif de notre fichier en paramètre
    public function assetExistsFunction(string $fileRelativePath)
    {
        // si le fichier passé en paramètre de la fonction existe, on retourne true, 
        // sinon on retourne false.
        return file_exists($this->kernelProjectDir."/public/".$fileRelativePath) ? true : false;
    }
}

Déclaration du service

Avec le système d'autowiring de Symfony, les services sont automatiquement enregistrés. Nous n'avons plus besoin de les déclarer comme avant. Un gain de temps, génial! En revanche, nous passons un paramètre au constructeur de notre AppExtension.php. Nous devons le renseigner, sinon Symfony va nous crier à la figure la prochaine fois que nous rechargerons notre page.

#config/services.yaml
services:
    _defaults:
        # Si vous avez l'habitude de déclarer un service et renseigner ses arguments, vous serez peut-être surpris par cette manière de faire.
        # Ici, plutôt que d'associer localement l'argument à notre service, nous le renseignons globalement dans notre application.
        # En effet, on peut dire que le kernelProjectDir est un paramètre récurrent dans le sens où c'est un paramètre souvent utilisé par un service.
        # Pour ce genre de paramètres, il est possible de définir des arguments par défaut comme ce que j'ai fait ci-dessous.
        # Désormais, dès lors que vous aurez besoin de ce paramètre dans d'autres services, vous n'aurez qu'à ajouter $kernelProjectDir à la liste des paramètres de leurs constructeurs. Magique!
        bind:
            $kernelProjectDir: "%kernel.project_dir%"
        # ... reste de la configuration

Utilisation de notre fonction

On va finir cet article par tester notre fonction dans un template. Pour cela, nous allons créer une route pour commencer:

<?php
// src/Controller/AppController.php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class AppController extends AbstractController
{
    /**
     * @Route("/", name="homepage")
     */
    public function index()
    {
        return $this->render('index.html.twig');
    }
}

Enfin le template:

{# templates/index.html.twig #}
<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Création de fonction Twig personnalisée</title>
    </head>
    <body>

        {# on passe le chemin relatif de notre ressource à la fonction #}
        {% if assetExists('images/mon-image.png') %}
            <img src="images/mon-image.png" alt="mon image">
        {% else %}
            <img src="images/image-par-defaut.png" alt="image par défaut">
        {% endif %}

    </body>
</html>

Pour finir, placer une image dans le dossier public/images/ et faites vos essais. Tout devrait fonctionner.