Aller au contenu principal
Retour au blog

Symfony TUI : construire des interfaces terminal riches

Flavien Métivier10 octobre 20269 min

Le composant Console fait partie de Symfony depuis ses tout débuts, mais il a toujours souffert de la même limite structurelle : il pense en termes de commande qui s'exécute, affiche un résultat, puis rend la main. Symfony 8.1, sorti au printemps 2026 avec un accent marqué sur les applications qui s'affranchissent du cycle HTTP — workers, CLI tools, pipelines de données —, introduit un composant qui change cette donne : Tui (Terminal User Interface). Contrairement à Console, Tui ne se contente pas d'afficher une barre de progression ou un tableau : il gère un véritable écran plein écran, qui se redessine en continu et réagit aux touches du clavier en temps réel, un peu comme le ferait une application desktop, mais dans un terminal. Ce composant reste jeune — ses premières versions stables datent du printemps 2026, avec des correctifs récents sur le découpage des lignes contenant des codes ANSI et sur le repaint complet de l'écran à la reprise après un détachement de multiplexeur type tmux ou screen — mais son architecture est déjà suffisamment posée pour qu'on s'y penche sérieusement. Cet article présente ce qu'apporte Tui par rapport à Console, dans quels contextes il a du sens, et un exemple minimal — clairement annoncé comme illustratif — pour se faire une idée concrète de l'API.

Console et Tui : deux composants, deux besoins différents

Le premier réflexe est de se demander si Tui remplace Console. Ce n'est pas le cas, et ce n'est pas son objectif. Console reste l'outil de référence pour tout ce qui relève de la commande classique : parsing d'arguments et d'options, sortie formatée, tables, barres de progression linéaires, questions interactives ponctuelles. Tui intervient sur un terrain différent : celui de l'interface plein écran, persistante, qui affiche plusieurs zones d'information simultanément et se met à jour en continu sans jamais réafficher la sortie ligne par ligne. Dans la pratique, les deux composants collaborent plutôt qu'ils ne s'excluent. Une commande Console classique peut très bien, à un moment de son exécution, lancer une session Tui pour une phase interactive — sélection dans une liste, saisie assistée, tableau de bord de suivi —, puis revenir à une sortie Console normale une fois la session terminée et le terminal restauré dans son état initial. Le namespace est Symfony\Component\Tui, avec la classe Tui comme point d'entrée principal. Contrainte technique importante à connaître avant toute évaluation : le composant s'appuie fortement sur les Fibers natives de PHP et sur la boucle d'événements Revolt, ce qui impose PHP 8.4 minimum. Sur un projet encore bloqué sur PHP 8.2 ou 8.3, Tui n'est donc simplement pas une option — un point à vérifier en premier avant de s'enthousiasmer sur la suite.

# Installation du composant (nécessite PHP 8.4+)
composer require symfony/tui

Ce qu'apporte Tui : une boîte à widgets complète

Ce qui frappe en premier lieu dans Tui, c'est l'étendue du catalogue de widgets fournis dès la version initiale — loin d'un simple wrapper autour de codes ANSI bruts. On y trouve de quoi construire une interface complète sans avoir à réinventer les briques de base :

  • TextWidget — labels, titres, et rendu de texte en gros caractères ASCII via FIGlet pour les bannières
  • InputWidget — champ de saisie mono-ligne avec curseur, défilement et collage
  • EditorWidget — éditeur multi-lignes avec retour à la ligne automatique, undo/redo et autocomplétion
  • SelectListWidget — liste filtrable et défilante, pour les sélections dans un jeu de données
  • SettingsListWidget et TabsWidget — panneaux de préférences et interfaces à onglets
  • MarkdownWidget — rendu Markdown complet avec coloration syntaxique des blocs de code
  • OverlayWidget — modales, menus déroulants et panneaux flottants au-dessus du reste de l'interface
  • LoaderWidget, CancellableLoaderWidget et ProgressBarWidget — indicateurs pour les opérations longues
  • ContainerWidget — composition en arbre, disposition verticale ou horizontale avec gestion des espacements

Chaque widget expose des événements typés — SubmitEvent, SelectEvent, ChangeEvent, CancelEvent, TabChangeEvent, FocusEvent — via l'EventDispatcher de Symfony, ce qui donne une sensation familière à quiconque a déjà branché des listeners sur un formulaire ou une commande. La gestion du focus suit un modèle classique de navigation clavier (cycle via F6/Shift+F6, focus programmatique, état :focused exploitable en style), et le système de keybindings permet de fusionner des raccourcis par défaut, des raccourcis au niveau de l'application et des surcharges par widget.

Exemple illustratif : une mini-application Tui

Voici un exemple volontairement minimal, construit à partir de l'API documentée par l'équipe Symfony, pour donner une idée concrète de ce à quoi ressemble une session Tui du point de vue du code. Il ne s'agit pas d'un extrait de production mais d'une illustration pédagogique : une invite de saisie qui affiche un message puis se termine proprement.

<?php
// Exemple illustratif — session Tui minimale
declare(strict_types=1);

use Symfony\Component\Tui\Tui;
use Symfony\Component\Tui\Widget\InputWidget;
use Symfony\Component\Tui\Widget\TextWidget;
use Symfony\Component\Tui\Event\InputEvent;
use Symfony\Component\Tui\Input\Keybindings;

$tui = new Tui();

$input = new InputWidget();
$input->setPrompt('Nom du service à superviser : ');
$input->onSubmit(fn () => $tui->stop());

$tui->add(new TextWidget('Saisis un nom de service puis valide avec Entrée :'));
$tui->add($input);
$tui->setFocus($input);

// Raccourci de sortie global, indépendant du widget qui a le focus
$keys = new Keybindings(['quit' => ['ctrl+c']]);
$tui->addListener(function (InputEvent $event) use ($tui, $keys) {
    if ($keys->matches($event->getData(), 'quit')) {
        $tui->stop();
    }
});

// Boucle d'événements bloquante jusqu'à l'appel de stop()
$tui->run();

// Le terminal est restauré ; on peut revenir à une sortie Console classique
echo sprintf("Supervision de « %s » démarrée.\n", $input->getValue());

Ce squelette illustre le point le plus structurant de Tui : run() bloque et prend la main sur l'écran jusqu'à ce qu'un widget ou un listener appelle stop(). Une fois la boucle arrêtée, le terminal est restauré dans son état initial, et le script peut reprendre une sortie standard, y compris via Console. C'est cette symétrie propre entre prise de contrôle et restauration qui rend l'intégration dans une commande existante relativement indolore.

Styliser une interface : feuilles de style et classes utilitaires

Le système de style de Tui emprunte volontairement au monde web plutôt qu'aux conventions habituelles du terminal. Trois approches coexistent, combinables selon les besoins : des objets Style appliqués directement sur un widget, des feuilles de style avec sélecteurs façon CSS (y compris des états comme :focused), et des classes utilitaires dans l'esprit Tailwind (p-2, bg-emerald-500, border-rounded). Les couleurs supportent l'ANSI classique, la palette 256 couleurs et le true color RGB, avec des fonctions de mélange (mix(), tint(), shade()) pour dériver des teintes cohérentes sans les redéfinir à la main partout.

Besoin d'un expert Symfony ?

Réserver un appel →
<?php
// Exemple illustratif — style d'un panneau via objet Style
declare(strict_types=1);

use Symfony\Component\Tui\Style\Style;
use Symfony\Component\Tui\Style\Border;
use Symfony\Component\Tui\Style\Padding;

$panel->setStyle(new Style(
    color: 'cyan',
    bold: true,
    padding: Padding::all(1),
    border: Border::all(1, 'rounded', 'cyan'),
));

// Équivalent partiel via feuille de style, avec état :focused
$stylesheet->addRule('.sidebar:focused', new Style(
    border: Border::all(1, 'rounded', 'cyan'),
));

// Équivalent via classes utilitaires directement sur le widget
$sidebar->addStyleClass('p-2');
$sidebar->addStyleClass('bg-emerald-500');
$sidebar->addStyleClass('border-rounded');

Fibers, Revolt et rendu non bloquant : le vrai apport architectural

Le point le plus intéressant de Tui, pour qui construit déjà des workers ou des outils qui tournent en tâche de fond, n'est pas la liste des widgets mais l'architecture d'exécution sous-jacente. Le composant s'appuie sur les Fibers et sur la boucle d'événements Revolt, ce qui lui permet de rester réactif — animations, curseur clignotant, mise à jour d'une barre de progression — pendant qu'une opération longue s'exécute en arrière-plan sans bloquer le thread principal. Un callback de tick, exécuté à chaque frame, permet de sonder l'état d'une opération asynchrone et de décider s'il faut continuer à rafraîchir l'écran à haute fréquence ou passer en veille pour économiser du CPU :

<?php
// Exemple illustratif — sonder une opération asynchrone à chaque tick
declare(strict_types=1);

use Symfony\Component\Tui\Event\TickEvent;

$tui->onTick(function (TickEvent $event) use ($future, $loader, $tui) {
    if ($future->isComplete()) {
        $loader->stop();

        return false; // opération terminée : on arrête de rafraîchir à haute fréquence
    }

    return true; // opération en cours : on continue de rafraîchir
});

Cette mécanique de tick, combinée à getDeltaTime() pour des animations indépendantes du taux de rafraîchissement réel, ouvre la porte à des tableaux de bord qui affichent l'avancement de plusieurs workers Messenger en parallèle, ou qui font défiler des logs sans jamais figer l'interface pendant l'attente d'une réponse réseau. C'est un terrain d'application naturel pour les équipes qui ont déjà investi dans des applications Symfony HTTP-less : un worker qui traite une file sans jamais charger de router n'a pas de raison de s'arrêter à une sortie Console linéaire s'il peut exposer un état vivant à l'opérateur qui le supervise.

Quand utiliser Tui, et quand s'en tenir à Console

Tui n'a pas vocation à devenir le réflexe par défaut de chaque commande Symfony. Son coût — apprentissage de l'API, contrainte PHP 8.4, complexité de test supérieure à une commande Console classique testable via CommandTester — se justifie sur un nombre de cas précis, mais ce nombre grandit vite dès qu'on regarde du côté des outils internes :

  • Tableaux de bord CLI de supervision — suivi en temps réel de plusieurs workers Messenger, de files RabbitMQ ou SQS, avec panneaux multiples et actualisation continue
  • Monitoring de tâches planifiées — un écran vivant au-dessus d'exécutions déclenchées par un scheduler, utile pour visualiser en direct l'état des tâches versionnées du composant Scheduler plutôt que de dépouiller des logs après coup
  • Outils internes interactifs — assistants de configuration en plusieurs étapes, explorateurs de données, sélecteurs de migrations ou d'environnements avant une opération sensible
  • Jeux et démonstrations techniques — un usage plus anecdotique, mais révélateur de la richesse du moteur de rendu et de la gestion du clavier

À l'inverse, une commande de déploiement, un script de migration one-shot ou un outil destiné à être appelé depuis un pipeline CI n'ont aucun intérêt à passer par Tui : ils doivent rester scriptables, avec une sortie prévisible et parsable, ce qui reste le terrain de Console. La bascule vers une architecture HTTP-less plus large — workers persistants, process supervisés — profite d'ailleurs aussi des gains de démarrage apportés par des runtimes comme le mode worker de FrankenPHP, dans une logique complémentaire : Tui rend l'observation humaine de ces processus plus riche, FrankenPHP les rend plus rapides à exécuter.

Ce qu'il faut savoir avant d'adopter Tui en production

Avant de généraliser Tui à des outils critiques, quelques réserves méritent d'être posées honnêtement. Le composant est récent : les correctifs publiés ces dernières semaines sur le découpage des lignes contenant des codes ANSI et sur le repaint complet à la reprise après détachement de multiplexeur montrent un composant encore en phase de stabilisation active, ce qui est normal pour une brique tout juste sortie mais à surveiller avant un usage exposé aux utilisateurs finaux. La contrainte PHP 8.4 minimum exclut de fait une partie du parc encore en 8.2 ou 8.3 — un point à vérifier avant toute expérimentation. Enfin, la stratégie de test d'une interface plein écran interactive diffère sensiblement de celle d'une commande Console classique : il n'existe pas encore d'équivalent aussi mature que CommandTester pour simuler des séquences de touches et vérifier le rendu à l'écran, ce qui pousse à commencer par des outils internes à faible enjeu plutôt que par une interface critique pour le métier. Ces réserves ne remettent pas en cause l'intérêt du composant, elles cadrent simplement où le risque doit rester mesuré tant que Tui n'a pas plusieurs cycles de release derrière lui.

Tui ouvre un terrain que Symfony n'adressait pas jusqu'ici : celui des interfaces terminal riches, pensées pour des opérateurs qui passent leurs journées à surveiller des workers, des files et des pipelines plutôt qu'à cliquer sur un dashboard web. Ce n'est pas un composant à généraliser du jour au lendemain, mais un outil à garder en tête dès qu'un projet accumule des scripts internes qui gagneraient à afficher un état vivant plutôt qu'un flux de logs. Si ton équipe construit ou fait évoluer des workers Messenger, des CLI internes ou plus largement une stack Symfony orientée HTTP-less, et que tu te demandes où Tui aurait vraiment sa place sans devenir un gadget, c'est exactement le genre d'arbitrage qu'un accompagnement Symfony ciblé permet de trancher rapidement, en fonction de l'existant réel du projet plutôt que d'un effet de mode.

Cet article vous a plu ? Partagez-le !

Besoin d'un expert Symfony ?

20 ans d'expérience sur l'écosystème PHP/Symfony.