Aller au contenu principal

Twig Components : la puissance du Backend dans le Frontend

Arrêtez les macros Twig et les includes. Passez aux Composants Twig : Classes PHP typées, logique encapsulée et réutilisabilité maximale.

Publié le

Temps de lecture 4 min

C'est l'histoire d'une frustration. D'un côté, nous avons Symfony et PHP : robustes, typés, orientés objet. De l'autre, nos templates Twig : des fichiers .html.twig qui finissent souvent en plat de spaghettis, avec des include imbriqués et des variables passées de manière implicite (le fameux "Ah zut, j'ai oublié de passer currentUser au header").

Pendant ce temps, nos amis du Frontend utilisent React/Vue. Ils ont des Composants. Des briques autonomes, isolées, qui portent leur propre logique.

Symfony a riposté avec Twig Components (symfony/ux-twig-component). Et spoiler : ça change tout.

Le problème des include et des macro

Avant, pour faire une "Card Produit" réutilisable, on avait deux choix médiocres :

  1. Le include : On dépend du contexte global. Si le template parent n'a pas la variable product, ça plante ou c'est vide. C'est fragile.
  2. La macro : C'est une fonction d'affichage pure. Mais si vous avez besoin de calculer un prix TTC ou de formater une date complexe, vous devez le faire avant d'appeler la macro. La logique fuit partout.

Le Composant = une classe au service du template

Un Twig Component, c'est l'union sacrée d'une Classe PHP et d'un Template.

La classe PHP

C'est ici que ça devient génial. C'est une classe PHP standard. Vous avez accès au Service Container ! Regardez mon composant Seo (src/Twig/Component/Utils/Seo.php). Il ne se contente pas d'afficher des balises, il construit la stratégie SEO.

PHP
#[AsTwigComponent(
    name: 'Utils:Seo',
    template: 'components/Utils/Seo.html.twig',
)]
final class Seo
{
    // On peut passer un Post ou une Page entière au composant
    public ?AbstractContent $content = null;

    private ?array $jsonLdSchema = null;
    private ?SeoDto $seoDto = null;

    public function __construct(
        private readonly StringHelper $stringHelper,
        private readonly JsonLdBuilder $jsonLdBuilder,
    ) {}

    #[ExposeInTemplate('seo')]
    public function getSeo(): SeoDto
    {
        if (!$this->seoDto instanceof SeoDto) {
            if ($this->content instanceof AbstractContent) {
                $baseSeo = $this->content->seo;
                // ... (Hydratation du DTO depuis le contenu)
                $this->seoDto = new SeoDto(/* ... */);
            } else {
                $this->seoDto = new SeoDtoFactory()->createEmpty();
            }
        }
        return $this->seoDto;
    }

    // Méthode calculée exposée au template
    #[ExposeInTemplate('jsonLdSchema')]
    public function getJsonLdSchema(): array
    {
        // On cache le résultat pour éviter de le recalculer
        if ($this->jsonLdSchema === null) {
            $this->jsonLdSchema = $this->jsonLdBuilder->build($this->content, $this->getDescription());
        }

        return $this->jsonLdSchema;
    }

    #[ExposeInTemplate('title')]
    public function getTitle(): string
    {
        // Logique de fallback intelligente pour le titre
        return $this->stringHelper->coalesce(
            $this->getSeo()->title,       // Titre SEO spécifique
            $this->content?->title,       // Titre du contenu
            'Le code est dans le <pre>' // Défaut
        );
    }
    
    // ... autres méthodes (getDescription, getOgTitle, etc.)
}

Le template Twig

Le template est d'une propreté absolue. Il ne fait pas de calculs. Il se contente d'afficher ce que l'Ingénieur lui a préparé.

Twig
{# components/Utils/Seo.html.twig #}
<title>{{ title }}</title>
<meta name="description" content="{{ description|striptags|trim }}">
<meta name="robots" content="{{ robots }}">

{# Open Graph #}
<meta property="og:title" content="{{ ogTitle }}">
<meta property="og:description" content="{{ ogDescription }}">
<meta property="og:type" content="{{ ogType }}">

{# Injection propre du JSON-LD calculé par le PHP #}
<script type="application/ld+json">
    {{ jsonLdSchema|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_UNESCAPED_SLASHES'))|raw }}
</script>

L'appel via la syntaxe HTML-like

Pour utiliser ce super-composant SEO sur n'importe quelle page, c'est une ligne :

Twig
<twig:Utils:Seo :content="post" />

Pourquoi c'est une révolution architecturale

Ce n'est pas juste de la poudre aux yeux, c'est un changement de paradigme.

  1. Encapsulation : Le composant est responsable de ses données. Si j'ai besoin de changer la façon dont le JSON-LD est généré, je touche la classe PHP, pas les 50 templates qui l'utilisent.
  2. Typage Fort : public ?AbstractContent $content. Si j'essaie de passer une chaîne de caractères à la place d'un objet Content, PHP explose immédiatement. Fini les bugs silencieux de types en Twig.
  3. Testabilité : Je peux tester unitaire ma classe Seo sans jamais rendre de HTML. Je vérifie que getTitle() renvoie bien le bon fallback. C'est impossible avec un include ou une macro.

Le mot de la fin

Twig Components n'essaie pas de transformer Symfony en React. Il essaie de ramener les bonnes pratiques du Backend (encapsulation, injection de dépendance, typage) dans la couche de vue. C'est la fin du code spaghetti dans les templates. Et il était temps.

J'utilise Google Analytics, Google Tag Manager et Microsoft Clarity pour améliorer votre expérience. Vous pouvez choisir les services que vous autorisez.

Le code est dans la boîte !

Vous recevrez bientôt les nouveaux billets dans votre boîte mail. Pas de spam, promis.

Désinscription possible à tout moment.