-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: article blog #1
base: main
Are you sure you want to change the base?
Changes from 3 commits
3c5e4cc
e98730a
3ec1e78
322902f
3fd38a2
8167274
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,304 @@ | ||||||
# Modernisez vos applications Symfony avec Tailwind et Twig Components | ||||||
|
||||||
Symfony est depuis longtemps un incontournable pour développer des applications web PHP. Mais qu’en est-il du frontend ? Grâce à Tailwind CSS et aux Twig Components, vous pouvez transformer vos interfaces en un rien de temps tout en restant organisé et maintenable. Dans cet article, découvrez comment combiner ces trois outils pour créer des applications Symfony à la fois modernes et élégantes. | ||||||
|
||||||
## Prérequis | ||||||
|
||||||
Une application Symfony >= 6.3 et Twig >= 3.0. Voir la documentation officielle : [Installing & Setting up the Symfony Framework (Symfony Docs)](https://symfony.com/doc/current/setup.html). | ||||||
|
||||||
Dans cet article, nous utiliserons une app Symfony 7.1 avec Symfony AssetMapper. Pas besoin d'installer Webpack Encore (bien que totalement compatible), ce qui simplifie l'installation. | ||||||
|
||||||
## Installation | ||||||
|
||||||
```bash | ||||||
composer require symfony/asset-mapper symfony/twig-bundle symfony/ux-twig-component | ||||||
``` | ||||||
|
||||||
Ceci installe les dépendances nécessaires pour utiliser les Twig Components. | ||||||
|
||||||
Ensuite, il faut installer Tailwind CSS : | ||||||
|
||||||
```bash | ||||||
composer require symfonycasts/tailwind-bundle tales-from-a-dev/twig-tailwind-extra | ||||||
php bin/console tailwind:init | ||||||
``` | ||||||
|
||||||
Le fichier `tailwind.config.js` sera créé à la racine de votre projet. Une bonne configuration de Tailwind est essentielle pour une utilisation optimale. | ||||||
|
||||||
### Conseil sur les conventions de nommage | ||||||
|
||||||
Dans notre équipe, lors d'une refonte graphique avec des maquettes Figma, nous n’avions pas défini de convention claire pour les variables de style. Résultat : des classes en `Camel-Case` (`bg-Grey-Light`), peu adaptées à Tailwind. Après discussion, nous avons opté pour une convention en `kebab-case` (`bg-grey-light`), respectant les conventions Tailwind et facilitant la maintenance. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. quelle équipe ? La notion d'équipe n'a pas été introduite, tu ne présentes pas un projet client ici Aussi, je ne comprends pas pourquoi figma est évoqué, pourquoi cet outil mérite-t-il sa place ici ? Il est lié à votre problématique de nommage ? |
||||||
|
||||||
## Utilisation | ||||||
|
||||||
Incluez le CSS de Tailwind dans le template de base : | ||||||
|
||||||
```twig | ||||||
{# templates/base.html.twig #} | ||||||
{% block stylesheets %} | ||||||
<link rel="stylesheet" href="{{ asset('styles/app.css') }}"> | ||||||
{% endblock %} | ||||||
``` | ||||||
|
||||||
En développement, utilisez : | ||||||
|
||||||
```bash | ||||||
php bin/console tailwind:build --watch | ||||||
``` | ||||||
|
||||||
Plus d'infos dans la [documentation officielle du bundle Tailwind.](https://symfony.com/bundles/TailwindBundle/current/index.html) | ||||||
|
||||||
```twig | ||||||
{# templates/default.html.twig #} | ||||||
{% extends 'base.html.twig' %} | ||||||
{% block body %} | ||||||
<div class="container"> | ||||||
<h1 class="text-3xl font-bold">Accueil</h1> | ||||||
<p class="text-gray-500">Bienvenue chez KNPlabs !</p> | ||||||
</div> | ||||||
{% endblock %} | ||||||
``` | ||||||
|
||||||
## Configuration avancée | ||||||
|
||||||
Tailwind est extrêmement flexible. Vous pouvez personnaliser les couleurs, polices, tailles, etc., dans le fichier `tailwind.config.js`. Par exemple, définissez la classe `container` pour centrer le contenu et ajouter du padding différents selon les tailles d’écran : | ||||||
|
||||||
```js | ||||||
theme: { | ||||||
container: { | ||||||
center: true, | ||||||
padding: { | ||||||
DEFAULT: '1rem', | ||||||
lg: '2rem', | ||||||
}, | ||||||
}, | ||||||
}, | ||||||
``` | ||||||
|
||||||
Ajoutez des couleurs personnalisées, raduis, etc. dans la clé `extend` : | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. radius |
||||||
|
||||||
```js | ||||||
extend: { | ||||||
colors: { | ||||||
background: "#ffffff", | ||||||
foreground: "#020817", | ||||||
primary: { | ||||||
DEFAULT: "#0f172a", | ||||||
foreground: "#f8fafc", | ||||||
}, | ||||||
secondary: { | ||||||
DEFAULT: "#f1f5f9", | ||||||
foreground: "#0f172a", | ||||||
}, | ||||||
}, | ||||||
borderRadius: { | ||||||
lg: "0.6rem", | ||||||
md: "0.4rem", | ||||||
}, | ||||||
}, | ||||||
``` | ||||||
|
||||||
Vous pouvez retrouver une configuration plus complète dans notre [repo de démonstration](https://github.com/KnpLabs/symfony-tailwind-twig-components/blob/main/tailwind.config.js). | ||||||
|
||||||
## Twig Components | ||||||
|
||||||
Twig Components, un composant Symfony, permet de créer des composants réutilisables dans vos templates Twig. Ces composants peuvent être : | ||||||
|
||||||
- Anonymes (Anonymous Twig Component) : fichiers Twig simples dédiés à l’UI. | ||||||
- Avec logique métier (Live Twig Component) : classes PHP pour des composants plus complexes, avec un template Twig associé. | ||||||
|
||||||
### Anonymous Twig Component et approche CVA | ||||||
|
||||||
Avec Twig Components, vous pouvez gérer les variantes de style CSS grâce à **CVA** (*Class Variant Authority*). | ||||||
|
||||||
Le CVA centralise la gestion des classes CSS conditionnelles, rendant vos composants modulaires et faciles à maintenir. Initialement popularisé dans le monde JavaScript, il est désormais disponible dans Twig ! | ||||||
|
||||||
Voici un exemple de création d’un bouton avec variantes : | ||||||
|
||||||
```twig | ||||||
{# templates/components/button.html.twig #} | ||||||
|
||||||
{% props as = 'button', variant = 'default', size = 'default' %} | ||||||
|
||||||
{% set buttonVariants = cva({ | ||||||
base: 'inline-flex items-center justify-center gap-2 w-fit whitespace-nowrap rounded-md text-sm font-medium transition-colors', | ||||||
variants: { | ||||||
variant: { | ||||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90', | ||||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | ||||||
}, | ||||||
size: { | ||||||
default: 'h-10 px-4 py-2', | ||||||
lg: 'h-12 rounded-md px-8', | ||||||
} | ||||||
}, | ||||||
defaultVariants: { | ||||||
variant: 'default', | ||||||
size: 'default', | ||||||
} | ||||||
}) %} | ||||||
|
||||||
<{{ as }} {{ attributes.without('class') }} | ||||||
class="{{ buttonVariants.apply({variant, size}, attributes.render('class'))|tailwind_merge }}" | ||||||
> | ||||||
{% block content '' %} | ||||||
</{{ as }}> | ||||||
``` | ||||||
|
||||||
Voici notre composant bouton qui accepte trois props : `as`, `variant`, et `size`. Cette approche de composant dite "polymorphic" permet de rendre par défaut le composant comme une balise `button`, mais vous pouvez le changer en `a`, `div`, ou autre en passant la prop `as`. | ||||||
|
||||||
#### Usage | ||||||
|
||||||
```twig | ||||||
<twig:button as="a" href="https://knplabs.com" size="lg">Découvrir KNPlabs</twig:button> | ||||||
``` | ||||||
|
||||||
#### Rendu HTML | ||||||
|
||||||
```html | ||||||
<a href="https://knplabs.com" class="inline-flex items-center justify-center gap-2 w-fit whitespace-nowrap rounded-md text-sm font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-12 px-8"> | ||||||
Découvrir KNPlabs | ||||||
</a> | ||||||
``` | ||||||
|
||||||
*Retrouvez un autre cas d'utilisation pour les headings sur notre [repo de démonstration](https://github.com/KnpLabs/symfony-tailwind-twig-components/blob/main/templates/components/heading.html.twig)* | ||||||
|
||||||
##### Bonus: Tailwind Merge | ||||||
|
||||||
Le filtre `tailwind_merge` permet de fusionner les classes CSS de manière intelligente. Par exemple, si vous surchager votre composant avec d'autre classes Tailwind en doublon ou en conflict, `tailwind_merge` les fusionnera et appliquera le style surchager en dernier. Cela évite l'utilisation pas très propre du `!` devant vos classes Tailwind. | ||||||
|
||||||
```twig | ||||||
<twig:button as="a" href="https://knplabs.com" size="lg" class="bg-red-500">Rouge KNPlabs</twig:button> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l'exemple n'a pas l'air d'illustré le filtre en question ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. J’ai un style par défaut avec une classe bg-primary, et ici je surcharge avec ma classe bg-red-500. Dans le résultat final, la classe bg-primary n’apparaît plus, donc les conflits entre les classes ont bien été résolus. Peut-être que l’exemple n’est pas assez explicite, ou peut-être même inutile pour cet article, je ne sais pas trop ? 🤷♂️ |
||||||
``` | ||||||
|
||||||
Rendu HTML: | ||||||
|
||||||
```html | ||||||
<a href="https://knplabs.com" class="inline-flex items-center justify-center gap-2 w-fit whitespace-nowrap rounded-md text-sm font-medium transition-colors h-12 px-8 bg-red-500"> | ||||||
Rouge KNPlabs | ||||||
</a> | ||||||
``` | ||||||
|
||||||
### Live Twig Components | ||||||
|
||||||
Comme je le disais, vous pouvez aussi créer des composants avec des classes PHP, pour des cas plus complexes nécessitant une certaine logique métier. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. c'est peut etre un abus de language de parler de "logique métier" ici ? Ça permet d'ajouter de la logique, métier ou non. |
||||||
|
||||||
Prenons un exemple très simple de formatage d'un prix. Imaginons que votre contrôleur vous renvoie un prix, et que vous voulez l'afficher en euros. Par exemple, `12.34` devrait être affiché comme `12,34 €`. | ||||||
|
||||||
Vous pouvez créer un Live Twig Component. Ce composant acceptera des propriétés `price` et `locale`, et expose une méthode `formatPrice`. | ||||||
|
||||||
```php | ||||||
<?php | ||||||
|
||||||
namespace App\Twig\Components; | ||||||
|
||||||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; | ||||||
use NumberFormatter; | ||||||
|
||||||
#[AsTwigComponent('product:price')] | ||||||
final class Price | ||||||
{ | ||||||
public float $price; | ||||||
public string $locale = 'fr_FR'; | ||||||
|
||||||
public function formatPrice(): string | ||||||
{ | ||||||
$formatter = new NumberFormatter($this->locale, NumberFormatter::CURRENCY); | ||||||
return $formatter->formatCurrency($this->price, 'EUR'); | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
Dans cet exemple, le attributes `#[AsTwigComponent('product:price')]` définit le nom du composant, qui sera automatiquement lié au fichier `templates/components/product/price.html.twig`. Voici à quoi pourrait ressembler le template Twig : | ||||||
|
||||||
```twig | ||||||
<span {{ attributes.without('class') }} | ||||||
class="w-fit bg-gray-300 rounded-full px-3 py-1 text-sm font-semibold text-gray-900 {{ attributes.render('class')|tailwind_merge }}" | ||||||
> | ||||||
{{ this.formatPrice }} | ||||||
</span> | ||||||
``` | ||||||
|
||||||
Dans ce template, la méthode `formatPrice` est appelée via `this.formatPrice`, permettant ainsi d'afficher le prix formaté de manière propre et réutilisable dans votre application. | ||||||
|
||||||
#### Pourquoi utiliser cette approche ? | ||||||
|
||||||
Avec cette approche, vous pouvez : | ||||||
|
||||||
- **Séparer la logique métier du rendu** : La méthode `formatPrice` encapsule la logique de formatage. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. je ne sais pas, implémenté dans un live component, je n'ai pas vraiment la sensation que la "logique est séparer du rendu", ça me semble assez couplé au contraire. Je suppose que tu veux dire qu'elle ne fait pas partie du template ? C'est déjà ce que font les filtres twig non ? Voici comment sont présentés les live components dans la doc symfony :
C'est présenté comme une solution pour ajouter de l'interactivité sur un composant, c'est assez loin de la description que tu en donnes ici. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. y'a pas une confusion, ce que tu décris ce sont des twig components non, pas des live components ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Effectivement, c’est peut-être un abus de dire que c’est un Live Twig Component juste parce qu'il a un petite logique. Je me suis sans doute un peu égaré entre les Anonymous Components, les Live Components, et les simples Twig Components sans nom particulié There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Par contre, quand je parle de séparation entre le rendu et la logique, je veux dire que d’un côté, tu as ton template avec le style, etc., et de l’autre, ta classe PHP qui gère la logique. Contrairement à certaines utilisations des extensions Twig qui retournent directement une string contenant des balises HTML. C’est pour illustrer ce point que je donne cet exemple. |
||||||
- **Tirer parti de la puissance de PHP** : Vous avez accès à toutes les fonctionnalités de PHP pour construire vos composants, comme ici avec `NumberFormatter`. | ||||||
|
||||||
Fini les Twig Extension qui retournent du HTML. Grâce à cette méthode, vous obtenez des composants puissants, clairs et réutilisables. | ||||||
|
||||||
#### Exemple d'utilisation | ||||||
|
||||||
Voici comment intégrer ce composant dans un template Twig : | ||||||
|
||||||
```twig | ||||||
<twig:product:price price="19.99" /> | ||||||
``` | ||||||
|
||||||
Rendu HTML : | ||||||
|
||||||
```html | ||||||
<span class="w-fit bg-gray-300 rounded-full px-3 py-1 text-sm font-semibold text-gray-900"> | ||||||
19,99 € | ||||||
</span> | ||||||
``` | ||||||
|
||||||
##### Bonus: Testez vos composants | ||||||
|
||||||
Vous pouvez tester vos composants Twig avec PHPUnit d'une simplicité deconcertente. Voici un exemple de test pour notre composant `Price`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```php | ||||||
<?php | ||||||
|
||||||
namespace App\Tests\Twig\Components; | ||||||
|
||||||
use App\Twig\Components\Price; | ||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||||
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents; | ||||||
|
||||||
class PriceTest extends KernelTestCase | ||||||
{ | ||||||
use InteractsWithTwigComponents; | ||||||
|
||||||
public function testComponentMount(): void | ||||||
{ | ||||||
$component = $this->mountTwigComponent( | ||||||
name: Price::class, | ||||||
data: ['price' => 99.99, 'locale' => 'fr_FR'], | ||||||
); | ||||||
|
||||||
$this->assertInstanceOf(Price::class, $component); | ||||||
$this->assertSame(99.99, $component->price); | ||||||
$this->assertSame('fr_FR', $component->locale); | ||||||
} | ||||||
|
||||||
public function testComponentRenders(): void | ||||||
{ | ||||||
$rendered = $this->renderTwigComponent( | ||||||
name: Price::class, | ||||||
data: ['price' => 99.99, 'locale' => 'fr_FR'], | ||||||
); | ||||||
|
||||||
$this->assertStringContainsString('99,99 €', $rendered); // Notez l'espace insécable entre le montant et le symbole de l'euro | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
Et voilà rien de plus compliqué que ca pour tester nos composants Twig. On test le mount du composant et on test le rendu du composant, tout simplement. | ||||||
|
||||||
## Limites | ||||||
|
||||||
- **Lisibilité du Code** : Twig peut parfois rendre le code plus verbeux, surtout avec des composants complexes ou lorsque plusieurs niveaux de composants sont imbriqués. La syntaxe de Twig n'est pas toujours la plus concise et peut rendre le DOM final difficile à appréhender, surtout pour les développeurs non familiers avec cette approche. Par exemple, l'utilisation de directives Twig à l’intérieur des attributs HTML pour manipuler des classes, des styles, etc., peut rapidement rendre le code difficile à lire. | ||||||
|
||||||
- **Séparation de la Logique et de la Présentation** : Lorsqu'on utilises à la fois des Twig Components et des classes PHP pour définir la logique, cela peut entraîner une certaine confusion. Par exemple, un composant Twig peut appeler une méthode définie par sa classe PHP, ce qui peut rendre la traçabilité du flux logique un peu floue, surtout pour des développeurs qui ne sont pas familiers avec ce type d'architecture. | ||||||
ErwannRousseau marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
- **Utilisation de Twig Extension vs Twig Components** : Twig permet créer des extensions ce qui peut simplifier certaines tâches. Additionné avec des Twig Components, Anonymous ou/et Live, cela peut créer un soucis d'harmonisation dans votre codebase. | ||||||
|
||||||
## Conclusion | ||||||
|
||||||
L’utilisation de Twig Components avec Tailwind CSS est une approche puissante et moderne pour développer le frontend d'un monolithe Symfony. Certes, cette méthodologie a ses limites, comme la lisibilité parfois complexe du DOM, dû aux parfoit nombreuses classes Tailwind et à Twig qui peut parfois rendre le code plus verbeux, ou bien la nécessité de trouver un équilibre entre logique PHP et Twig. Cependant, ces défis peuvent être surmontés avec une bonne organisation, un découpage rigoureux des composants et des conventions claires. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dû aux parfoit parfois There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ce que je veux dire, c’est que ce n’est pas systématique d’utiliser une multitude de classes Tailwind pour styliser une balise, ce qui pourrait alourdir le HTML. |
||||||
|
||||||
Selon notre d'expérience, nous sommes pleinement satisfaits de cette approche. Elle nous a permis d'intégrer nos maquettes de manière de plus en plus rapide et efficace grâce à une bibliothèque de composants que nous avons enrichie au fil du projet. Cette productivité accrue n’aurait pas été possible sans Tailwind CSS, qui simplifie et accélère grandement le stylage de nos composants et du reste de notre interface. En somme, cette combinaison s'est révélée être un excellent choix pour nos besoins. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
tournure un peu étrange ? Nous sommes pleinement satisfaits de notre expèrience de cette approche ?
faut peut-etre pas s'enflammer, imho il vaudrait mieux parler d'une aide/d'un gain de productivité apporté par Tailwind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pour les prérequis pas totalement certain des versions minimum
je vais devoir verifier ca
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ça me semble cohérent en tout cas mais oui vaut mieux double check