Voltar ao blog

Componentes Twig no Symfony: Construindo um Design System em PHP

Por Jonathan Bufon

Olá pessoas! Assim como um bom programador, estava em busca de conhecimento e recentemente participei de uma palestra dos meus colegas David e da Julia na IXC Soft sobre Design System — uma palestra incrível.

Para quem não conhece, um Design System é basicamente uma biblioteca de componentes reutilizáveis que mantém a consistência visual e funcional de uma aplicação. Pense em botões, alertas, modais e cards que você usa repetidamente — em vez de reescrever HTML e CSS toda vez, você cria componentes que podem ser chamados onde precisar. Isso facilita manutenção, garante consistência e acelera o desenvolvimento.

No entanto, uma curiosidade ficou na minha mente na parte de componentes. Foram citados os mais famosos frameworks como React e Vue.js, mas e PHP? Ficou para trás?

A Descoberta dos Twig Components

Na curiosidade — já que eu nunca tinha ouvido falar em desenvolver componentes HTML em PHP — pensei em reinventar essa roda usando Twig, um local onde você consegue gerenciar e gerar componentes de forma dinâmica. Mas pesquisando um pouco, percebi que cheguei meio atrasado.

O Twig (gerenciador de templates do Symfony) já possui um pacote chamado symfony/ux-twig-component. A instalação é simples via Composer:

composer require symfony/ux-twig-component

Pronto! O pacote está instalado e pronto para uso. Para mais detalhes e recursos avançados, consulte a documentação oficial do Symfony UX Twig Components.

Criando Seu Primeiro Componente

Agora vem o ponto interessante: com o console do Symfony você pode criar um componente, por exemplo:

php bin/console make:twig-component Alert

Esse comando cria alguns arquivos no seu projeto. Vamos falar sobre eles:

  • src/Twig/Components/Alert.php
  • templates/components/Alert.html.twig

Entendendo o Alert.php

Esse arquivo é a classe do componente, onde colocamos a lógica: funções, variáveis etc. Alguns pontos importantes:

  • Variáveis públicas: todas podem ser usadas diretamente no template do componente
  • Método construtor: não é __construct, e sim mount. O mount é chamado primeiro e funciona como um construtor ou um setUp
Código completo do componente Alert.php no editor
Estrutura da classe Alert.php com validação de tipos

💡 Nota Importante

É um exemplo simples, mas funcional. Observe o default no switch — isso garante que se alguém passar um tipo inválido (como "danger"), o sistema lançará uma exceção clara em vez de renderizar um componente vazio. Sempre valide entradas!

Em produção, você poderia usar um Enum (PHP 8.1+) para os tipos ou criar constantes de classe, tornando o código ainda mais robusto.

Construindo o Template Alert.html.twig

Originalmente o arquivo do componente vem desta maneira:

<div{{ attributes }}>
    <!-- component HTML -->
</div>

Depois de montar o HTML do componente usando um pouco de CSS e as variáveis públicas, o meu arquivo Alert.html.twig ficou assim:

<div{{ attributes }}>
    <style>
        .alert-custom {
            display: block;
            padding: 14px 18px;
            border-radius: 8px;
            border: 2px solid {{ colorText }};
            background: {{ colorBackground }};
            color: {{ colorText }};
            font-family: system-ui, sans-serif;
            font-size: 0.95rem;
            line-height: 1.4;
        }

        .alert-custom strong {
            font-weight: 600;
            margin-right: 4px;
        }
    </style>

    <div class="alert-custom">
        <strong>{{ title }}</strong> {{ description }}
    </div>
</div>

⚠️ Importante sobre Estilização

CSS inline aqui é apenas didático! Em produção, use TailwindCSS, classes CSS externas ou CSS Modules. Colocar <style> dentro do componente polui o HTML e dificulta manutenção.

Quanto à lógica, em aplicações reais você provavelmente buscaria dados de uma entidade, API ou serviço, em vez de hardcoded no componente. O objetivo aqui é mostrar a estrutura e possibilidades.

Configurando o Controller

Criei um controller básico só para rodar uma rota "Home":

<?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('/', name: 'home')]
    public function home(): Response
    {
        return $this->render('home/home.html.twig');
    }
}

Usando o Componente no Template

Para chamar componentes Twig no template, existem duas formas:

<twig:Alert />

ou

{{ component('Alert') }}

A comunidade costuma usar o padrão <twig:Alert />, então mantive ele:

{% extends 'base.html.twig' %}

{% block body %}

    <h1>Página inicial</h1>

    {% set return = 'info' %}

    {% if return == 'error' %}
        <twig:Alert type="error" />
    {% endif %}

    {% if return == 'warning' %}
        <twig:Alert type="warning" />
    {% endif %}

    {% if return == 'success' %}
        <twig:Alert type="success" />
    {% endif %}

    {% if return == 'info' %}
        <twig:Alert type="info" />
    {% endif %}

{% endblock %}

Os 4 Tipos de Alertas Renderizados

Veja como cada tipo de alerta é renderizado no navegador:

Alerta tipo Info renderizado
Alert tipo Info
Alerta tipo Error renderizado
Alert tipo Error
Alerta tipo Warning renderizado
Alert tipo Warning
Alerta tipo Success renderizado
Alert tipo Success

Como Funciona na Prática

Se você é bom em lógica, já percebeu onde isso chega. Ao alterar o valor da variável definida em {% set return = '' %}, o componente correspondente é chamado. O valor passa para o type, que aciona o mount(), que chama a validação, define os atributos e finalmente renderiza o componente com o resultado esperado.

🔍 Passando Variáveis

Falando um pouco mais sobre essa chamada do componente, você já deve ter percebido que a variável type está sendo passada logo após <twig:Alert. Isso é um padrão do componente.

Em outros casos, sem usar a função que valida o type, eu poderia ter passado todas as variantes dentro da chamada do componente, desta maneira:

<twig:Alert 
    title="Sucesso!" 
    description="Ação realizada com sucesso!" 
    colorBackground="#90EE90" 
    colorText="green" 
/>

Casos de Uso Reais

Mas afinal, onde usar Twig Components no dia a dia? Aqui vão alguns exemplos práticos:

🎯 Aplicações Práticas

  • Sistema de Notificações: Exibir mensagens de feedback após ações do usuário (cadastro, login, exclusão, etc). Em vez de repetir HTML em vários templates, um simples <twig:Alert type="success" /> resolve.
  • Validação de Formulários: Mostrar erros de validação de forma consistente. Cada campo com erro pode renderizar um componente de alerta específico.
  • Cards de Produtos/Notícias: Se você tem uma lista de produtos, cards de blog ou portfólio, componentes garantem que todos terão a mesma estrutura, facilitando mudanças futuras.
  • Elementos de UI Reutilizáveis: Botões, badges, modais, tooltips — qualquer coisa que se repete na sua aplicação é candidata a virar componente.
  • Dashboards e Relatórios: Componentes de gráficos, estatísticas e widgets que você reutiliza em diferentes páginas administrativas.

No meu caso, já estou pensando em usar isso no Gatepass (sistema de gestão de ingressos) para padronizar alertas de validação e cards de eventos. A manutenção fica muito mais fácil quando você altera em um lugar e reflete em todo o sistema.

Conclusão e Próximos Passos

Os Twig Components provam que PHP não ficou para trás quando o assunto é Design System e componentização. Com o Symfony, temos ferramentas poderosas para criar interfaces modulares e reutilizáveis, seguindo os mesmos princípios dos frameworks JavaScript modernos.

Este foi um overview prático focado no básico. Mas há muito mais a explorar, como:

  • LiveComponents: Adicionar interatividade em tempo real sem JavaScript
  • Computed Properties: Propriedades calculadas dinamicamente
  • Slots: Passar conteúdo HTML customizado para componentes
  • PreMount/PostMount Hooks: Controlar o ciclo de vida dos componentes

📚 Recursos Adicionais

Espero que tenham gostado do conteúdo. Caso queiram discutir sobre, não hesitem em comentar no post do LinkedIn. Até mais!