Jdi na navigaci předmětu

Symfony Console

Úvod

  • knihovna pro konzolové aplikace - oproti ručnímu zpracování $argv nebo getopt() řeší automaticky
    • nápovědu (--help) a seznam příkazů
    • kontrolu povinných argumentů a převod hodnot na správný typ
    • srozumitelné chybové hlášky
  • instalace

    composer require symfony/console
  • dokumentace
  • konzolová aplikace Symfony\Component\Console\Application
  • jednotlivé příkazy lze psát dvěma způsoby
    • invokable command - třída s metodou __invoke(), argumenty a přepínače se definují pomocí atributů (od Symfony 7.3)
    • starší způsob - třída dědí od Symfony\Component\Console\Command\Command a implementuje configure() a execute()

Ukázka

$ php cli.php image:resize --help
Description:
  Resize image

Usage:
  image:resize [options] [--] <input>

Arguments:
  input                 Input image file

Options:
  -w, --width=WIDTH     Output image width [default: 640]
  ...

$ php cli.php image:resize

  Not enough arguments (missing: "input").

image:resize [-w|--width WIDTH] [--] <input>

Aplikace

  • jednotlivé příkazy se zaregistrují pomocí addCommand()

    // cli.php
    require 'vendor/autoload.php';
    
    use Symfony\Component\Console\Application;
    
    $app = new Application();
    $app->addCommand(new ResizeCommand()); // viz Invokable command
    // ...
    $app->run();
  • v plném Symfony frameworku se příkazy zaregistrují automaticky
  • spuštění příkazu - název příkazu, za ním argumenty a přepínače
  • bez názvu příkazu se vypíše seznam všech příkazů (stejně jako list)

    $ php cli.php image:resize picture.png
    $ php cli.php list

Invokable command

  • atribut AsCommand a metoda __invoke()
  • parametr SymfonyStyle $io slouží pro výstup, viz Výstup - SymfonyStyle
  • argumenty a přepínače se definují jako parametry metody pomocí atributů Argument a Option

    use Symfony\Component\Console\Attribute\Argument;
    use Symfony\Component\Console\Attribute\AsCommand;
    use Symfony\Component\Console\Attribute\Option;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Style\SymfonyStyle;
    
    #[AsCommand(name: 'image:resize', description: 'Resize image')]
    class ResizeCommand
    {
        public function __invoke(
            SymfonyStyle $io,
            #[Argument(description: 'Input image file')] string $input,
            #[Option(description: 'Output image width', shortcut: 'w')] int $width = 640,
        ): int {
            $io->writeln("Resizing $input to width $width");
            return Command::SUCCESS;
        }
    }

Název, popis, nápověda

  • nastavují se v atributu AsCommand
    • name - název příkazu
    • description - popis (zobrazí se ve výpisu příkazů)
    • help - nápověda (--help)

      #[AsCommand(
          name: 'image:resize',
          description: 'Resize image',
          help: 'Resize PNG or JPEG and save result to new file',
      )]

Příkazy se často člení do skupin podle :, při volání stačí použít jednoznačnou zkratku

$ php cli.php i:r picture.png

Argumenty a přepínače

  • argumenty a přepínače jsou parametry metody __invoke() označené atributem #[Argument], resp. #[Option]
  • Symfony většinu informací zjistí z deklarace parametru
    • název - z názvu parametru převedeného na kebab-case ($maxWidth → --max-width), lze změnit pomocí name
    • povinnost - podle toho, zda má parametr výchozí hodnotu
    • výchozí hodnota - z výchozí hodnoty parametru (zobrazí se i v --help)
    • typ - z typehintu (proto je povinný)
      • hodnota z příkazové řádky se na daný typ převede (int, float, bool, array, backed enum)
      • neplatnou hodnotu Symfony odmítne s chybovou hláškou (např. --max-width=abc pro int)
  • v atributu se uvádí jen to, co z parametru zjistit nelze, např. popis (description)
  • backed enum jako typ - omezí hodnotu na výčet
    • přípustné hodnoty jsou hodnoty jednotlivých case
    • do parametru se předá rovnou instance enumu (např. Format::Jpeg)
    • při neplatné hodnotě chybová hláška vypíše přípustné hodnoty

      enum Format: string
      {
          case Png = 'png';
          case Jpeg = 'jpeg';
      }
      
      // --format=png nebo --format=jpeg, jiná hodnota skončí chybou
      #[Option(description: 'Output format')] Format $format = Format::Png,
  • Understanding how Console Arguments and Options Are Handled

Argumenty

  • atribut #[Argument] (dokumentace)
  • bez výchozí hodnoty povinný, s výchozí hodnotou (nebo nullable) volitelný
  • typ array - více hodnot
  • není nutné explicitně uvádět (ne)povinnost

    // povinný argument
    #[Argument(description: 'Input image file')] string $input,
    
    // volitelný argument
    #[Argument(description: 'Output image file')] ?string $output = null,

Přepínače

  • atribut #[Option] (dokumentace)
  • musí mít výchozí hodnotu
  • typ bool - přepínač bez hodnoty, s výchozí hodnotou true i v negované podobě (--color / --no-color)
  • typ array - více hodnot
  • krátká varianta pomocí shortcut

    // -w320, -w 320, --width 320, --width=320
    #[Option(description: 'Output image width', shortcut: 'w')] int $width = 640,
    
    // --force
    #[Option(description: 'Overwrite output')] bool $force = false,
    
    // --color, --no-color
    #[Option(description: 'Use colors')] bool $color = true,

Provádění příkazu

  • do __invoke() lze kromě argumentů a přepínačů nechat předat i další objekty, rozhoduje typ parametru (pořadí parametrů nehraje roli)
    • SymfonyStyle - pohodlný výstup (writeln(), success(), error(), table(), …​)
    • OutputInterface, InputInterface - přímý přístup ke vstupu a výstupu
  • výstup viz SymfonyStyle
  • návratová hodnota musí být int (exit kód), např. Command::SUCCESS, Command::FAILURE, Command::INVALID

Výstup - SymfonyStyle

  • jednotný vzhled výstupu všech příkazů (dokumentace)
  • text - writeln(), title(), section(), listing(), table()

    $io->title('Image info');
    $io->table(['File', 'Width', 'Height'], [
        ['photo.png', 640, 480],
        ['logo.png', 128, 128],
    ]);
    Image info
    ==========
    
     ----------- ------- --------
      File        Width   Height
     ----------- ------- --------
      photo.png   640     480
      logo.png    128     128
     ----------- ------- --------
  • zprávy - success(), error(), warning(), info()
  • progress bar - progressIterate($iterable)
  • interakce - ask(), confirm(), choice()

    $format = $io->choice('Output format', ['png', 'jpeg'], 'png');
    if (!$io->confirm("Convert $input to $format?")) {
        return Command::FAILURE;
    }
    $io->success("Saved to image.$format");
    $ php cli.php image:convert photo.png
     Output format [png]:
      [0] png
      [1] jpeg
     > jpeg
    
     Convert photo.png to jpeg? (yes/no) [yes]:
     > yes
    
     [OK] Saved to image.jpeg

Method commands

  • od Symfony 8.1 lze atribut AsCommand použít i na metodu - jedna třída tak může definovat více příkazů
  • název ve třídním atributu slouží jako prefix (vznikne image:resize a image:crop)

    #[AsCommand(name: 'image')]
    class ImageCommands
    {
        #[AsCommand(name: 'resize', description: 'Resize image')]
        public function resize(
            SymfonyStyle $io,
            #[Argument] string $input
        ): int { /* ... */ }
    
        #[AsCommand(name: 'crop', description: 'Crop image')]
        public function crop(
            SymfonyStyle $io,
            #[Argument] string $input
        ): int { /* ... */ }
    }
    
    $commands = new ImageCommands();
    $app->addCommand($commands->resize(...));
    $app->addCommand($commands->crop(...));

Starší způsob

  • stále podporovaný a častý v existujícím kódu
  • nutný pro Symfony starší než 7.3

Dědění od Command

  • nastavení v metodě configure(), provádění v metodě execute()

    #[AsCommand(name: 'image:resize', description: 'Resize image')]
    class ResizeCommand extends Command
    {
        protected function configure(): void
        {
            $this
                ->setHelp('Resize PNG or JPEG and save result to new file')
                ->addArgument('input', InputArgument::REQUIRED, 'Input image file')
                ->addOption('width', 'w', InputOption::VALUE_REQUIRED, 'Output image width', 640)
            ;
        }
    
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $output->writeln('Resizing ' . $input->getArgument('input')
                . ' to width ' . $input->getOption('width'));
            return Command::SUCCESS;
        }
    }

Argumenty a přepínače

  • argumenty - addArgument(název, režim, popis, výchozí hodnota)
    • InputArgument::REQUIRED, InputArgument::OPTIONAL - povinný, volitelný
    • InputArgument::IS_ARRAY - více hodnot, kombinuje se pomocí | (např. InputArgument::REQUIRED | InputArgument::IS_ARRAY)
  • přepínače - addOption(název, krátká varianta, režim, popis, výchozí hodnota)
    • InputOption::VALUE_NONE - přepínač bez hodnoty (--force)
    • InputOption::VALUE_REQUIRED, InputOption::VALUE_OPTIONAL - hodnota povinná, nepovinná
    • InputOption::VALUE_IS_ARRAY - více hodnot, kombinuje se pomocí | s VALUE_REQUIRED nebo VALUE_OPTIONAL
    • InputOption::VALUE_NEGATABLE - přepínač lze zadat i v negované podobě (--color / --no-color)
  • vstup
    • $input->getArgument('…​')
    • $input->getOption('…​')

Výstup

  • $output->write('…​')
  • $output->writeln('…​')
  • SymfonyStyle lze vytvořit i ručně - $io = new SymfonyStyle($input, $output), viz Výstup - SymfonyStyle