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
Dans le cadre d’une refonte complète de son architecture Web, Expertissim a sollicité l’expertise de JoliCode afin de tenir les délais et le niveau de qualité attendus. Le domaine métier d’Expertissim n’est pas trivial : les spécificités du marché de l’art apportent une logique métier bien particulière et un processus complexe. La plateforme propose…
Nous avons développé une plateforme de site génériques autour de l’API Phraseanet. À l’aide de Silex de composants Symfony2, nous avons accompagné Alchemy dans la réalisation d’un site déclinable pour leurs clients. Le produit est intégralement configurable et supporte de nombreux systèmes d’authentification (Ldap, OAuth2, Doctrine ou anonyme).
Afin de poursuivre son déploiement sur le Web, Arte a souhaité être accompagné dans le développement de son API REST “OPA” (API destinée à exposer les programmes et le catalogue vidéo de la chaine). En collaboration avec l’équipe technique Arte, JoliCode a mené un travail spécifique à l’amélioration des performances et de la fiabilité de l’API. Ces…