Skip to Content

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 le stimulus-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-bundle

Les 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ôleur clipboard à ce <div>.
  • data-clipboard-target="source" : cet élément est la target source (accessible via this.sourceTarget).
  • data-action="click->clipboard#copy" : au clic, appeler la méthode copy() 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éé dans connect() (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. Un connect() sans disconnect() propre fuit. (C’est l’équivalent du useEffect cleanup 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 :

PackageCe qu’il apporte
symfony/ux-chartjsgraphiques (Chart.js) rendus depuis une config PHP
symfony/ux-autocompletechamps de formulaire avec autocomplétion Ajax
symfony/ux-dropzonezone de dépôt de fichiers
symfony/ux-cropperjsrecadrage d’image
symfony/ux-notifynotifications navigateur
composer require symfony/ux-chartjs

Puis, 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 ?

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.