Programmation PHP avec Symfony/Commande

Principe

modifier

Les commandes sont, avec les contrôleurs, les seuls points d'entrée permettant de lancer le programme. Ce sont aussi des services mais elles se lancent via la console (en CLI).

La liste des commandes disponibles en console est visible avec :

  • Sur Linux :
bin\console
  • Sur Windows :
php bin\console

 

Dans Symfony 2 c'était php app\console.

Parmi les principales commandes natives au framework et à ses bundles, on trouve :

  • php bin/console list : liste toutes les commandes du projet.
  • php bin/console debug:router : liste toutes les routes (URL) du site.
  • php bin/console debug:container : liste tous les services avec leurs alias (qui sont des instanciations des classes).
  • php bin/console debug:container --parameters : liste les paramètres.
  • php bin/console debug:container --env-vars : liste les variables d'environnement.
  • php bin/console debug:autowiring --all : liste tous les services automatiquement déclarés.
  • php bin/console debug:config NomDuBundle : liste tous les paramètres disponibles pour paramétrer un bundle donné. Ex : bin/console debug:config FrameworkBundle
  • php bin/console cache:clear : vide la mémoire cache du framework.
  • php bin/console generate:bundle : crée un bunble (surtout pour SF2).
  • php bin/console generate:controller : crée un contrôleur (en SF2).
  • php bin/console doctrine:migrations:generate; chown 1001:1001 -R app/DoctrineMigrations : génère un fichier vide de migration SQL ou DQL.
  • php bin/console doctrine:migrations:list : liste les noms des migrations disponibles (utiles car selon la configuration on doit les appeler par leur namespace ou juste par numéro).

Toutes les commandes peuvent être abrégées, par exemple "doctrine:migrations:generate" fonctionne avec "d:m:g" ou "do:mi:ge".

Créer une commande

modifier

Lors du lancement d'une commande, on distingue deux types de paramètres[1] :

  1. Les arguments : non nommés
  2. Les options : nommées.

Exemple :

bin/console app:ma_commande argument1 --option1=test
#[AsCommand(name: 'app:ma_commande')]
class HelloWorldCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addArgument(
                'argument1',
                InputArgument::OPTIONAL,
                'Argument de test'
            )
            ->addOption(
                'option1',
                null,
                InputOption::VALUE_OPTIONAL,
                'Option de test'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        echo 'Hello World! '.$input->getOption('option1').' '.$input->getArgument('argument1');

        return self::SUCCESS;
    }
}

NB : en SF < 6.1, remplacer l'attribut AsCommand par une propriété connue de la classe mère :

   protected static $defaultName = 'app:ma_commande';

Pour définir un argument tableau, utiliser InputArgument::IS_ARRAY et séparer les valeurs par un espace. Ex :

 bin/console app:my_command arg1.1 arg1.2 arg1.3

Ajout de logs

modifier

Pour que la commande logue ses actions, la documentation de Symfony propose deux solutions[2] :

  • $output->writeln()
  • $io = new SymfonyStyle($input, $output);

Cette deuxième option permet aussi d'afficher une barre de progression, ou d'interagir avec l'utilisateur :

$io->confirm(Êtes vous sûr de vouloir faire ça ? (Yes/No)');
$io->choice('Choisissez l\'option', ['première ligne', 'toutes les lignes'])

Ensuite il y a plusieurs niveaux de log pouvant colorer la console qui le permet :

$io->info('Commentaire');
$io->success('Succès');
$io->warning('Warning');
$io->error('Echec');

Toutefois ce n'est pas conforme à la PSR3[3] et si on veut utiliser ces logs comme ceux des autres services (pour les stocker ailleurs par exemple), mieux vaut utiliser LoggerInterface $logger (en plus c'est horodaté).

Pour affichage les logs dans la console, utiliser le paramètre -v :

  • -v affiche tous les logs "NOTICE" ou supérieurs.
  • -vv les "INFO".
  • -vvv les "DEBUG", c'est le mode le plus verbeux possible.

Tester une commande

modifier

Ex :

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

/**
 * @see https://symfony.com/doc/current/console.html#testing-commands
 */
class CommandTest extends KernelTestCase
{
    public function testExecute()
    {
        $kernel = self::bootKernel();
        $monService = static::getContainer()->get('test.mon_service_public'); // En Symfony 6.3 on n'est plus obligé de créer un service public pour le test

        $application = new Application($kernel);

        $command = $application->find('app:ma_commande');
        $commandTester = new CommandTester($command);

        $commandTester->execute(
            [
                '--option1' => 'option1',
                '--dry-run' => 'true',
            ]
        );

        $commandTester->assertCommandIsSuccessful();

        $output = $commandTester->getDisplay();
        $hasErrors = str_contains($output, 'ERROR');
        $this->assertFalse($hasErrors, $output);
    }
 L'option "dry-run" est recommandée pour éviter que la commande modifie des ressources externes, comme une base de données ou une API. Il faut pour cela que la commande en tienne compte au moment d'appeler ces ressources.

 

Le getDisplay affiche ce que l'on voit sur le dernier écran de la console (cela n'affiche pas tout l'output). Pour voir les logs de Monolog, il faut ajouter les lignes suivantes dans la commande[4] :

        if ($this->logger instanceof Logger) {
            $this->logger->pushHandler(new ConsoleHandler($output));
        }

Références

modifier