veilletech.fr
17 sept. Feed du jour
#05 SYMFONY Article

Symfony 8.2 : des sous-commandes à la Docker

Le jour où votre CLI interne a trois niveaux, le nom n'est plus le problème. L'arbre l'est.

Symfony 8.2 ajoute les sous-commandes séparées par des espaces (`tenant users import`) à côté des namespaces à deux-points historiques. Ce n'est pas un alias : chaque niveau analyse ses propres options, une commande parente peut exister sans execute() pour porter des options communes, et l'objet CommandChain donne à l'enfant l'entrée de son parent.

3 min de lectureintermédiairevidéo 1:18
Partager
Sommaire5 sections
  1. Ce qui se passe
  2. Ce que la forme espacée change vraiment
  3. Lire l'entrée du parent
  4. Un arbre entier dans une classe
  5. À retenir

Ce qui se passe

Les applications console Symfony groupent leurs commandes avec des deux-pointscache:clear, messenger:consume. Docker et Git ont retenu l'autre convention : des espaces, où chaque niveau analyse ses propres options (docker compose up).

Symfony 8.2 permet la seconde forme, sans retirer la première. Les deux invocations suivantes lancent la même commande :

Terminal
php bin/console tenant:users:import customers.csv --dry-run
php bin/console tenant users import customers.csv --dry-run

Contribution de Nicolas Grekas, PR #65825 pour les sous-commandes et #65853 pour les groupes.

Ce que la forme espacée change vraiment

Ce n'est pas un alias. Dans la forme espacée, chaque niveau enregistré analyse ses propres options, et seule la dernière commande s'exécute :

Terminal
php bin/console tenant --name=acme users import customers.csv --dry-run

Avec les deux-points, tout est analysé d'un bloc, à la fin.

Trois conséquences à connaître :

  1. Tous les niveaux n'ont pas besoin d'une commande. Si seul tenant:users:import est enregistré, users est un nœud implicite. Cela fonctionne donc sur n'importe quel namespace existant, sans rien écrire.
  2. Une commande sans code liste ses sous-commandes et sort avec le code 1 — exactement comme un namespace nu aujourd'hui.
  3. Les sous-commandes ont toujours la priorité sur les arguments. Avant 8.2, deploy rollback passait rollback comme valeur de l'argument target. Pour retrouver ce comportement, il faut -- :
Terminal
php bin/console deploy -- rollback

--help, help, list et l'autocomplétion comprennent l'arbre : tenant users import --help affiche l'aide de import, et tenant users im<TAB> complète.

Lire l'entrée du parent

Une sous-commande n'hérite pas des options de ses parents. Elle accède à la chaîne des commandes résolues via l'objet CommandChain, injectable comme n'importe quel service de console :

PHP
public function __invoke(
    CommandChain $chain,
    #[Argument] string $file,
): int {
    $tenantName = $chain->getInput('tenant')?->getOption('name');
    // getInput(TenantCommand::class) fonctionne aussi
}

L'opérateur nullsafe n'est pas décoratif : le parent ne fait partie de la chaîne que dans la forme espacée. Lancée directement, tenant:users:import renvoie null sur getInput('tenant').

Hors de la commande — dans un écouteur d'événement, par exemple — Application::getCommandChain() rend le même objet pendant l'exécution.

Les options de l'application (-v, --env) restent acceptées à n'importe quel niveau. Côté tests, ApplicationTester suit, parce que ArrayInput accepte désormais des valeurs positionnelles qui remplissent les arguments dans l'ordre.

Un arbre entier dans une classe

Symfony 8.1 avait déjà permis de définir des commandes comme méthodes d'une classe. La 8.2 complète le dispositif :

PHP
#[AsCommand('tenant', description: 'Manages tenants', options: [
    new InputOption('name', null, InputOption::VALUE_REQUIRED, 'The tenant name'),
])]
class TenantCommands
{
    #[AsCommand('users:import', description: 'Imports users from a CSV file')]
    public function importUsers(
        CommandChain $chain,
        #[Argument] string $file,
        #[Option] bool $dryRun = false,
    ): int { }
}

Cette classe enregistre deux commandes : le groupe tenant, sans code, dont la description et l'option --name viennent de l'attribut, et tenant:users:import.

L'entrée options: n'est d'ailleurs pas réservée aux groupes : une commande invocable peut désormais déclarer ses options là plutôt qu'en paramètres de méthode avec #[Option], et les lire depuis l'objet d'entrée comme dans une classe Command classique.

À retenir

Le gain est pour l'outillage interne : un CLI d'administration à trois niveaux redevient lisible, avec l'aide et la complétion à chaque étage. Symfony 8.2 n'est pas encore sortie — c'est la série des nouveautés annoncées pendant son développement.