Использование Symfony Console: от простых команд до продвинутых сценариев

Раздел: Фреймворк Symfony -> Symfony консоль

Основы работы с Symfony Console

Как создать простую команду в Symfony?

Наиболее распространённый способ – создать класс, наследуемый от Command, и зарегистрировать его как сервис. Команда автоматически обнаруживается через автоконфигурацию, если файл находится в src/Command.


// src/Command/HelloCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:hello')]
class HelloCommand extends Command
{
    protected function configure(): void
    {
        $this->setDescription('Prints a greeting.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Hello, Symfony Console!');
        return Command::SUCCESS;
    }
}

Php symfony console (symfony консоль)

Альтернативный вариант – регистрация команды через services.yaml вручную, если автоконфигурация отключена.


# config/services.yaml
services:
    App\Command\HelloCommand:
        tags: ['console.command']

Проблема: Команда не отображается в списке. Решение: Проверить, что класс реализует Command правильно, и очистить кеш командой php bin/console cache:clear. Если используется атрибут #[AsCommand], убедиться, что PHP-версия >= 8.0.

Как добавить аргументы и опции в консольную команду?

Аргументы и опции определяются в методе configure() с помощью объекта InputArgument и InputOption. Основной подход – использование fluent-интерфейса addArgument() и addOption().


protected function configure(): void
{
    $this->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?')
         ->addOption('yell', 'y', InputOption::VALUE_NONE, 'Output greeting in uppercase');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $name = $input->getArgument('name');
    $text = "Hello, $name!";
    if ($input->getOption('yell')) {
        $text = strtoupper($text);
    }
    $output->writeln($text);
    return Command::SUCCESS;
}

Можно задать аргумент с несколькими значениями (InputArgument::IS_ARRAY) или опцию, принимающую значение (InputOption::VALUE_REQUIRED).


// Аргумент-массив
$this->addArgument('items', InputArgument::IS_ARRAY, 'List of items (space separated)');
// Опция с обязательным значением
$this->addOption('format', null, InputOption::VALUE_REQUIRED, 'Output format (json|xml)');

Ошибка: Неправильное число аргументов. Решение: Убедиться, что для REQUIRED аргументов указаны значения. Для опций с VALUE_REQUIRED не забыть передать значение после ключа.

Как организовать интерактивный ввод в консоли?

Использование вспомогательного класса Symfony\Component\Console\Question\Question и QuestionHelper. Основной способ – получить helper из `$this->getHelper('question')` и задать вопрос.


use Symfony\Component\Console\Question\Question;

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $helper = $this->getHelper('question');
    $question = new Question('Please enter your name: ', 'Guest');
    $name = $helper->ask($input, $output, $question);
    $output->writeln("Hello, $name!");
    return Command::SUCCESS;
}

Для выбора из списка используется ChoiceQuestion, для скрытого ввода пароля – Question с установкой setHidden(true).


use Symfony\Component\Console\Question\ChoiceQuestion;

$question = new ChoiceQuestion('Select color', ['red', 'green', 'blue'], 'red');
$color = $helper->ask($input, $output, $question);

Проблема: Вопрос не отображается. Решение: Убедиться, что output не подавлен (например, при использовании `-q`). Для тестов можно передавать input с готовыми ответами.

Как внедрить сервисы в консольную команду?

Использовать конструктор с типизированными параметрами – Symfony автоматически внедрит зависимости благодаря автовайрингу. Команда должна быть зарегистрирована как сервис (по умолчанию это происходит автоматически).


use Psr\Log\LoggerInterface;

class GreetCommand extends Command
{
    public function __construct(private LoggerInterface $logger)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->logger->info('Greet command executed');
        $output->writeln('Hello!');
        return Command::SUCCESS;
    }
}

Можно использовать сеттер-инъекцию через #[Required] атрибут, если требуется необязательная зависимость.


use Symfony\Contracts\Service\Attribute\Required;

#[Required]
public function setLogger(LoggerInterface $logger): void
{
    $this->logger = $logger;
}

Ошибка: Сервис не найден. Решение: Убедиться, что сервис зарегистрирован в контейнере и autowire включён. Проверить, что класс команды находится в src/Command и не исключён из автоконфигурации.

Как протестировать консольную команду?

Использовать CommandTester из пространства имён Symfony\Bundle\FrameworkBundle\Test\KernelTestCase. Этот класс позволяет выполнять команду в изолированном окружении и проверять вывод.


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

class HelloCommandTest extends KernelTestCase
{
    public function testExecute(): void
    {
        $kernel = self::bootKernel();
        $application = new Application($kernel);
        $command = $application->find('app:hello');
        $commandTester = new CommandTester($command);
        $commandTester->execute([]);

        $output = $commandTester->getDisplay();
        $this->assertStringContainsString('Hello, Symfony Console!', $output);
    }
}

Можно передавать аргументы и опции в метод execute() вторым параметром. Для проверки кода возврата используется $commandTester->getStatusCode().


$commandTester->execute(['command' => 'app:hello', 'name' => 'World']);
$this->assertEquals(Command::SUCCESS, $commandTester->getStatusCode());

Проблема: Тест падает с ошибкой кеша. Решение: Перед тестами выполнить php bin/console cache:clear --env=test. Использовать отдельную базу данных для тестов.

Расширенные примеры использования Symfony Console

Пример 1: Команда с прогресс-баром

Для отображения прогресса длительных операций используется ProgressBar.

Пример

use Symfony\Component\Console\Helper\ProgressBar;

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $progressBar = new ProgressBar($output, 100);
    $progressBar->start();

    for ($i = 0; $i < 100; $i++) {
        usleep(50000); // имитация работы
        $progressBar->advance();
    }

    $progressBar->finish();
    $output->writeln('');
    return Command::SUCCESS;
}
 100/100 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

Пример 2: Табличный вывод

Использование Table для форматирования данных.

Пример

use Symfony\Component\Console\Helper\Table;

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $table = new Table($output);
    $table->setHeaders(['ID', 'Name', 'Email'])
          ->setRows([
              [1, 'Alice', 'alice@example.com'],
              [2, 'Bob', 'bob@example.com'],
              [3, 'Charlie', 'charlie@example.com'],
          ])
          ->render();
    return Command::SUCCESS;
}
+----+---------+------------------+
| ID | Name    | Email            |
+----+---------+------------------+
| 1  | Alice   | alice@example.com |
| 2  | Bob     | bob@example.com   |
| 3  | Charlie | charlie@example.com |
+----+---------+------------------+

Пример 3: Использование нескольких команд в одном классе (сублимированный подход)

Хотя каждая команда обычно представляет собой отдельный класс, можно создать команду, которая сама вызывает другие команды через Application.

Пример

use Symfony\Component\Console\Application;

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $application = new Application();
    $application->add(new HelloCommand());
    $application->add(new GoodbyeCommand());
    // выполнить, например, в зависимости от аргумента
    return Command::SUCCESS;
}

Пример 4: Команда с подписчиками событий

Можно обрабатывать события консоли (например, ConsoleEvents::COMMAND) для логирования или модификации.

Пример

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\ConsoleEvents;

class ConsoleSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [ConsoleEvents::COMMAND => 'onCommand'];
    }

    public function onCommand(ConsoleCommandEvent $event): void
    {
        $command = $event->getCommand();
        // логирование или изменение ввода
    }
}

Пример 5: Команда с цветным выводом

Использование методов OutputInterface для форматирования.

Пример

$output->writeln('<info>Information message</info>');
$output->writeln('<comment>Comment message</comment>');
$output->writeln('<error>Error message</error>');
(в консоли текст будет окрашен в зелёный, жёлтый, красный соответственно)

Symfony консоль - comments

En
Php symfony console (php)