Chapitre 8.1 — Stimulus
Partie 8 : Symfony UX — Chapitre 1/4 Version de référence : Stimulus 3 / Symfony UX / Symfony 7.4.
⏱️ TL;DR
- Stimulus est un framework JS modeste (issu de Hotwire/Basecamp) : on attache des contrôleurs à du HTML existant via des attributs
data-*. Le HTML reste la source de vérité.- Un contrôleur vit dans
assets/controllers/et est auto-enregistré par lestimulus-bundle.- Trois concepts : targets (éléments ciblés), values (données typées passées depuis le HTML), actions (
data-action="click->hello#greet").- Idéal pour du comportement ciblé (toggle, copie, menu, compteur) sans construire une SPA.
- Symfony UX fournit des contrôleurs prêts (graphiques, autocomplete, dropzone, cropper…).
- Pour un dev Next.js : Stimulus n’a pas de virtual DOM ni d’état applicatif global — c’est l’anti-React : on saupoudre du comportement sur du HTML rendu serveur.
🎯 Objectifs
- Écrire un contrôleur Stimulus (targets, values, actions).
- Le brancher sur du HTML via
data-*. - Comprendre la philosophie « HTML source de vérité ».
- Utiliser les contrôleurs UX préfabriqués.
1. La philosophie : HTML d’abord
React construit le DOM depuis le JS (l’état est la source de vérité, le HTML est un produit). Stimulus fait l’inverse : le HTML existe déjà (rendu par Twig), et Stimulus vient y attacher du comportement. Il ne « possède » pas le DOM — il réagit à des éléments annotés.
Conséquence : pas de build lourd, pas de state management, du HTML robuste et référençable (SEO), enrichi de JS là où c’est utile.
2. Installer
composer require symfony/stimulus-bundleLes contrôleurs vont dans assets/controllers/ et sont auto-découverts (pas d’enregistrement manuel). Un contrôleur hello_controller.js est disponible sous le nom hello.
3. Anatomie d’un contrôleur
// assets/controllers/clipboard_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['source', 'button']; // éléments référencés
static values = { successText: String }; // données typées venant du HTML
copy() {
navigator.clipboard.writeText(this.sourceTarget.value);
this.buttonTarget.textContent = this.successTextValue || 'Copié !';
}
}On le branche sur du HTML (rendu par Twig) :
<div data-controller="clipboard" data-clipboard-success-text-value="Copié dans le presse-papier">
<input data-clipboard-target="source" value="https://booklab.test/r/abc123" readonly>
<button data-clipboard-target="button"
data-action="click->clipboard#copy">Copier le lien</button>
</div>Décodage des attributs :
data-controller="clipboard": attache le contrôleurclipboardà ce<div>.data-clipboard-target="source": cet élément est la targetsource(accessible viathis.sourceTarget).data-action="click->clipboard#copy": au clic, appeler la méthodecopy()du contrôleur.data-clipboard-success-text-value="...": passe une value typée (this.successTextValue).
4. Le cycle de vie
Un contrôleur a des méthodes de cycle de vie : connect() (quand il s’attache au DOM), disconnect() (quand il en part), et xxxValueChanged() (quand une value change). connect() est le bon endroit pour initialiser (un timer, une lib tierce).
export default class extends Controller {
connect() {
this.timer = setInterval(() => this.tick(), 1000);
}
disconnect() {
clearInterval(this.timer); // nettoyage : évite les fuites
}
tick() { /* ... */ }
}⚠️ Piège : ne jamais oublier de nettoyer dans
disconnect()ce qu’on a créé dansconnect()(timers, écouteurs globaux, instances de libs). Avec Turbo (chapitre 8.2), les pages sont remplacées sans rechargement complet → un contrôleur peut se connecter/déconnecter souvent. Unconnect()sansdisconnect()propre fuit. (C’est l’équivalent duuseEffectcleanup en React.)
5. Les contrôleurs UX préfabriqués
Symfony UX fournit des packages qui livrent leur contrôleur Stimulus prêt à l’emploi. Vous installez un composant PHP, et le contrôleur JS est automatiquement disponible :
| Package | Ce qu’il apporte |
|---|---|
symfony/ux-chartjs | graphiques (Chart.js) rendus depuis une config PHP |
symfony/ux-autocomplete | champs de formulaire avec autocomplétion Ajax |
symfony/ux-dropzone | zone de dépôt de fichiers |
symfony/ux-cropperjs | recadrage d’image |
symfony/ux-notify | notifications navigateur |
composer require symfony/ux-chartjsPuis, dans un contrôleur PHP, on construit un graphique et Twig le rend — le JS est géré pour vous. C’est le confort « batteries incluses » de l’écosystème Symfony côté front.
💡 Pour un dev Next.js : Stimulus vous semblera « rétro » comparé à React — mais c’est volontaire. Pour un back-office, ajouter un toggle, une copie, un autocomplete via
data-*est plus rapide et plus léger que monter un composant React, un state, un effet. Réservez React à ce qui a vraiment besoin d’un état riche côté client (le front public Next.js). Pour le reste, Stimulus suffit et reste maintenable.
✏️ Exercices
Exercice 1. Écrivez un contrôleur Stimulus toggle qui affiche/masque un élément cible au clic sur un bouton.
✅ Solution
// assets/controllers/toggle_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['content'];
toggle() {
this.contentTarget.hidden = !this.contentTarget.hidden;
}
}<div data-controller="toggle">
<button data-action="click->toggle#toggle">Détails</button>
<div data-toggle-target="content" hidden>Contenu caché…</div>
</div>Exercice 2. Pourquoi faut-il nettoyer dans disconnect() ce qu’on a créé dans connect(), surtout avec Turbo ?
✅ Solution
Parce que Turbo remplace les pages sans rechargement complet : un contrôleur peut se connecter et déconnecter de nombreuses fois pendant la navigation. Si connect() crée un timer/écouteur global/instance de lib sans que disconnect() le supprime, ces ressources s’accumulent (fuites mémoire, comportements dupliqués). disconnect() est l’équivalent du cleanup de useEffect en React : indispensable.
Exercice 3. Vous devez ajouter un graphique à un tableau de bord admin. React ou Stimulus/UX ? Justifiez.
✅ Solution
Stimulus / symfony/ux-chartjs : le back-office est rendu serveur (Twig), le graphique est un ajout ciblé ; on configure les données en PHP et le contrôleur Stimulus fourni les affiche, sans monter de SPA ni de build React. Monter React pour un seul graphique dans un admin serveur serait disproportionné. On garde React pour le front public Next.js à état riche.
🧠 Quiz de révision
1. Quelle est la philosophie de Stimulus vis-à-vis du DOM ?
Le HTML est la source de vérité : Stimulus attache du comportement à du HTML existant (rendu serveur) via des attributs data-*, au lieu de générer le DOM depuis un état JS.
2. À quoi servent targets, values et actions ?
Targets = éléments référencés (this.xTarget) ; values = données typées passées depuis le HTML (this.xValue) ; actions = liaison événement→méthode (data-action="click->ctrl#method").
3. Où placent-on les contrôleurs et comment sont-ils enregistrés ?
Dans assets/controllers/ ; ils sont auto-découverts par le stimulus-bundle (pas d’enregistrement manuel).
4. Quel est l’équivalent du cleanup useEffect en Stimulus ?
useEffect en Stimulus ?La méthode disconnect(), où l’on nettoie ce que connect() a créé (timers, écouteurs, instances).
5. Comment ajoute-t-on un graphique sans écrire de JS ?
Via symfony/ux-chartjs : on configure le graphique en PHP, Twig le rend, et le contrôleur Stimulus fourni gère l’affichage.
Prochain chapitre : 8.2 — Turbo — navigation SPA-like, frames et streams sans écrire de JS.