Comprendre (enfin) les TTY et PTY avec le composant Process de Symfony
Vous est-il déjà arrivé de lancer une commande (composer, rsync ou une commande Symfony) directement dans votre terminal pour y admirer de jolies barres de progression colorées, mais de constater que cette même commande, une fois exécutée via un script PHP, perdait soudainement tout son formatage ?
C’est un grand classique lorsque l’on utilise le composant Process de Symfony. Pour comprendre l’origine de ce comportement (et surtout comment y remédier), il faut plonger un instant dans la façon dont Linux gère les flux et les terminaux. Rassurez-vous, c’est plus simple qu’il n’y paraît.
Section intitulée retour-aux-bases-les-flux-standardsRetour aux bases : les flux standards
Sous Linux, chaque processus dispose par défaut de trois flux standards, identifiés par des descripteurs de fichiers (file descriptors) :
- 0 : STDIN (l’entrée standard)
- 1 : STDOUT (la sortie standard)
- 2 : STDERR (la sortie d’erreur standard)
Ce qu’il faut retenir, c’est que ces flux se comportent comme de simples tuyaux. Ce qui se trouve au bout du tuyau détermine le comportement du programme. Généralement, on rencontre trois scénarios :
- Un fichier : Par exemple, lorsque vous redirigez une sortie avec
ls > file.txt; - Un PTY / TTY (Terminal) : Lorsque vous exécutez la commande directement devant votre écran ;
- Un pipe (tube) : Lorsque vous enchaînez des commandes (
ls | grep php) ou que vous lancez un sous-processus de manière programmatique.
Section intitulée le-quot-probleme-quot-de-l-execution-programmatiqueLe « problème » de l’exécution programmatique
Par défaut, quand vous utilisez le composant Process pour lancer une commande, Symfony utilise des pipes (notre troisième scénario).
Le programme exécuté (prenons Composer) est intelligent : il analyse ce qui se trouve au bout du tuyau de sa sortie standard (STDOUT). S’il détecte un pipe au lieu d’un terminal, il en déduit qu’il est exécuté par une machine ou un script. Pour éviter de polluer d’éventuels fichiers de logs avec des caractères invisibles (les fameux codes ANSI qui génèrent les couleurs) ou des barres de progression illisibles, il bascule automatiquement en mode « texte brut ».
C’est pour cette raison exacte que vos couleurs disparaissent.
Section intitulée la-methode-code-settty-true-code-le-lien-directLa méthode setTty(true) : le lien direct
Le composant Process propose une première solution avec la méthode setTty(true).
En l’activant, vous branchez directement les flux de votre sous-processus sur le vrai terminal de votre système (celui depuis lequel vous avez lancé votre script PHP).
La conséquence : Les couleurs et les animations sont de retour. La commande s’affiche exactement comme si vous l’aviez tapée vous-même.
La limite : Puisque le flux est branché directement sur votre écran, votre script PHP n’y a plus accès. La synchronisation automatique fait que l’affichage est immédiat, mais il devient impossible de capturer la sortie avec un $process->getOutput() pour l’inspecter.
Section intitulée la-magie-de-code-setpty-true-code-l-illusion-parfaiteLa magie de setPty(true) : l’illusion parfaite
C’est ici qu’interviennent les Pseudo-Terminaux (PTY).
Lorsque vous utilisez setPty(true), vous demandez au système de créer un terminal émulé de toutes pièces. Un PTY fonctionne comme un duo composé d’un contrôleur et d’un terminal virtuel :
- Le script PHP (via Symfony Process) agit comme le contrôleur.
- Le processus enfant (votre commande) est branché sur le terminal virtuel émulé (qui prend la forme d’un fichier dynamique, souvent
/dev/pts/X).
Pour le processus enfant, l’illusion est totale. Il détecte bien un terminal au bout de son tuyau et génère donc ses couleurs et ses barres de progression interactives.
Côté PHP, en tant que « contrôleur » du PTY, la donne change : l’envoi vers l’écran n’est plus automatique (il agit comme un buffer). C’est à vous de lire ce qui sort du terminal virtuel. Vous retrouvez ainsi le meilleur des deux mondes : vous forcez le programme à conserver son affichage riche, tout en gardant la capacité d’intercepter et de manipuler le flux sortant directement dans votre code PHP.
Section intitulée demo-timeDémo time !
On commence avec une commande Symfony qui affiche, si l’exécuteur le permet, des choses en couleur :
#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication;
new SingleCommandApplication()
->setCode(function (OutputInterface $output): int {
$output->writeln('<info>Hello World!</info>');
$output->writeln('<comment>This is a single command application.</comment>');
$output->writeln('<error>Goodbye!</error>');
return 0;
})
->run();
Ensuite, nous exécutons cette commande Symfony, avec le composant Process. En fonction des arguments, nous activons ou non PTY ou TTY :
<?php
require __DIR__.'/vendor/autoload.php';
$process = new Symfony\Component\Process\Process([__DIR__ . '/console.php']);
$process->setTty(($argv[1] ?? '') === 'tty');
$process->setPty(($argv[1] ?? '') === 'pty');
$process->mustRun();
echo "\nOutput captured:\n";
dump($process->getOutput());
Et voici le résultat :

Section intitulée en-resumeEn résumé
Si vous construisez des outils en ligne de commande ou des workers asynchrones en PHP :
- Par défaut (Pipes) : À privilégier pour les tâches de fond où la sortie doit être parsée ou logguée proprement, sans caractères d’échappement ;
setTty(true): Idéal si vous voulez simplement déléguer l’affichage et l’interactivité à l’utilisateur, sans avoir besoin d’analyser la sortie côté PHP ;setPty(true): La solution de choix pour forcer un affichage riche (couleurs, animations) tout en conservant le contrôle du flux sortant dans votre script.
Commentaires et discussions
Ces clients ont profité de notre expertise
Cacharel, marque emblématique du prêt-à-porter féminin et du parfum, s’associe à JoliCode pour accélérer son virage digital et renouer avec les jeunes femmes après trois ans d’absence. Avec une nouvelle direction artistique, l’objectif est de proposer une collection moderne tout en restant fidèle à l’héritage de la marque. Pour accompagner cette renaissance, …
Dans le cadre du renouveau de sa stratégie digitale, Orpi France a fait appel à JoliCode afin de diriger la refonte du site Web orpi.com et l’intégration de nombreux nouveaux services. Pour effectuer cette migration, nous nous sommes appuyés sur une architecture en microservices à l’aide de PHP, Symfony, RabbitMQ, Elasticsearch et Docker.
Nous avons entrepris une refonte complète du site, initialement développé sur Drupal, dans le but de le consolider et de jeter les bases d’un avenir solide en adoptant Symfony. La plateforme est hautement sophistiquée et propose une pléthore de fonctionnalités, telles que la gestion des abonnements avec Stripe et Paypal, une API pour l’application…