Использование Symfony Console: от простых команд до продвинутых сценариев
Основы работы с 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>');
(в консоли текст будет окрашен в зелёный, жёлтый, красный соответственно)