Symfony/Console для PHP: полное руководство с примерами
1. Что это и зачем нужна
Symfony/Console — это мощный и гибкий компонент фреймворка Symfony, предназначенный для создания консольных (CLI) команд на языке PHP. Он предоставляет разработчикам полный набор инструментов для построения интерактивных, структурированных и легко тестируемых скриптов командной строки. Вместо того чтобы писать примитивные скрипты с использованием глобальных переменных и функций, Symfony/Console предлагает объектно-ориентированный подход, основанный на классах команд, вводе/выводе (Input/Output) и системе вопросов.
Зачем это нужно? В современной веб-разработке консольные команды выполняют критически важные задачи: миграции баз данных, запуск очередей, генерация кода, очистка кэша, отправка email-рассылок и многие другие фоновые процессы. Symfony/Console позволяет стандартизировать эти задачи, делая их легко читаемыми, расширяемыми и поддерживаемыми. Библиотека автоматически обрабатывает аргументы и опции команд, генерирует справку, форматирует вывод и поддерживает цветовое оформление, что значительно упрощает жизнь разработчика.
Более того, Symfony/Console не привязан жестко к фреймворку Symfony. Его можно использовать в любом PHP-проекте (Laravel, Yii, чистый PHP) как отдельный компонент. Это делает его универсальным стандартом де-факто для создания CLI-инструментов в экосистеме PHP. Благодаря встроенной поддержке тестирования (через CommandTester) и событийной модели, вы можете легко писать юнит-тесты для своих команд и расширять их функциональность с помощью событий.
2. Установка
Установка производится через менеджер зависимостей Composer. Откройте терминал в корне вашего проекта и выполните команду:
composer require symfony/console
Если вы используете Symfony Flex (стандартный для Symfony 4+), пакет будет автоматически настроен. Для проектов без Symfony вам потребуется создать файл bin/console (или любое другое имя) и инициализировать приложение вручную.
После установки убедитесь, что в вашем composer.json появилась запись:
{
"require": {
"symfony/console": "^6.0"
}
}
3. Быстрый старт — минимальный рабочий пример
Создадим простейшую команду, которая выводит "Hello, World!". Сначала создадим файл bin/console:
#!/usr/bin/env php
<?php
// bin/console
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
// Создаем приложение
$application = new Application('My CLI App', '1.0.0');
// Регистрируем команду
$application->register('hello')
->setDescription('Выводит приветствие')
->setCode(function (InputInterface $input, OutputInterface $output) {
$output->writeln('Hello, World!');
});
// Запускаем приложение
$application->run();
Теперь сделайте файл исполняемым и запустите:
chmod +x bin/console
php bin/console hello
Вы увидите в консоли: Hello, World!
Это минимальный пример. В реальных проектах команды создаются как отдельные классы, наследующие от Command, что обеспечивает лучшую организацию кода.
4. Основные методы/функции/классы
Рассмотрим ключевые классы и методы библиотеки. Для каждого приведена сигнатура, описание и пример.
4.1. Application::__construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
Что делает: Создает новое консольное приложение. Это главный класс, который управляет всеми командами.
use Symfony\Component\Console\Application;
$app = new Application('MyApp', '2.0.0');
// Теперь $app готов принимать и запускать команды
4.2. Application::add(Command $command)
Что делает: Регистрирует объект команды в приложении. Команда становится доступной для запуска.
use Symfony\Component\Console\Command\Command;
class MyCommand extends Command {
protected static $defaultName = 'app:my-command';
protected function configure() {
$this->setDescription('My custom command');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$output->writeln('Command executed!');
return Command::SUCCESS;
}
}
$app = new Application();
$app->add(new MyCommand());
$app->run();
4.3. Command::configure()
Что делает: Абстрактный метод, который вызывается для настройки команды. Здесь задаются имя, описание, аргументы и опции.
protected function configure(): void
{
$this
->setName('user:create')
->setDescription('Создает нового пользователя')
->addArgument('username', InputArgument::REQUIRED, 'Имя пользователя')
->addOption('admin', null, InputOption::VALUE_NONE, 'Сделать пользователя администратором');
}
4.4. Command::execute(InputInterface $input, OutputInterface $output)
Что делает: Основной метод, где выполняется логика команды. Должен вернуть целочисленный код завершения (0 — успех, 1 — ошибка).
protected function execute(InputInterface $input, OutputInterface $output): int
{
$username = $input->getArgument('username');
$isAdmin = $input->getOption('admin');
$output->writeln("Создан пользователь: $username");
if ($isAdmin) {
$output->writeln("Пользователь назначен администратором.");
}
return Command::SUCCESS; // или 0
}
4.5. InputInterface::getArgument(string $name)
Что делает: Получает значение аргумента команды по его имени. Аргументы — это обязательные или опциональные параметры, передаваемые в команду.
// В configure:
$this->addArgument('email', InputArgument::REQUIRED, 'Email пользователя');
// В execute:
$email = $input->getArgument('email');
$output->writeln("Email: $email");
4.6. InputInterface::getOption(string $name)
Что делает: Получает значение опции (флага) команды. Опции обычно передаются с префиксом --.
// В configure:
$this->addOption('format', null, InputOption::VALUE_REQUIRED, 'Формат вывода', 'json');
// В execute:
$format = $input->getOption('format');
$output->writeln("Выбран формат: $format");
4.7. OutputInterface::writeln(string|array $messages)
Что делает: Выводит строку (или массив строк) в консоль с автоматическим добавлением символа новой строки.
$output->writeln('Простая строка');
$output->writeln(['Строка 1', 'Строка 2', 'Строка 3']); // Каждая с новой строки
$output->writeln('<info>Зеленый текст</info>'); // Использование тегов форматирования
4.8. OutputInterface::write(string|array $messages, bool $newline = false)
Что делает: Выводит строку без добавления новой строки в конце. Полезно для прогресс-баров или интерактивного ввода.
$output->write('Загрузка: ');
for ($i = 1; $i <= 10; $i++) {
$output->write('.');
usleep(100000); // Имитация работы
}
$output->writeln(' Готово!');
4.9. QuestionHelper::ask(InputInterface $input, OutputInterface $output, Question $question)
Что делает: Задает пользователю вопрос и получает ответ. Поддерживает автодополнение, валидацию и скрытый ввод (для паролей).
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Helper\QuestionHelper;
$helper = $this->getHelper('question');
$question = new Question('Пожалуйста, введите ваше имя: ', 'Гость');
$name = $helper->ask($input, $output, $question);
$output->writeln("Привет, $name!");
4.10. ProgressBar::advance(int $step = 1)
Что делает: Увеличивает прогресс-бар на указанное количество шагов. Используется для отображения прогресса длительных операций.
use Symfony\Component\Console\Helper\ProgressBar;
$progressBar = new ProgressBar($output, 50);
$progressBar->start();
for ($i = 0; $i < 50; $i++) {
usleep(50000); // Имитация работы
$progressBar->advance();
}
$progressBar->finish();
$output->writeln(''); // Новая строка после прогресс-бара
5. Реальный практический пример
Создадим полноценную команду для генерации отчета по пользователям из базы данных (симулированной). Команда будет принимать аргументы и опции, выводить прогресс и форматировать результат.
// src/Command/GenerateUserReportCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class GenerateUserReportCommand extends Command
{
protected static $defaultName = 'app:generate-report';
protected static $defaultDescription = 'Генерирует отчет по пользователям';
protected function configure(): void
{
$this
->setDescription('Генерирует отчет по пользователям')
->setHelp('Эта команда генерирует отчет по всем активным пользователям системы.')
->addArgument('filename', InputArgument::REQUIRED, 'Имя файла для сохранения отчета')
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Формат отчета (csv/json)', 'csv')
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Максимальное количество пользователей', '100')
->addOption('verbose', 'v', InputOption::VALUE_NONE, 'Подробный вывод');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$filename = $input->getArgument('filename');
$format = $input->getOption('format');
$limit = (int) $input->getOption('limit');
$isVerbose = $input->getOption('verbose');
// Подтверждение действия
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion(
"Вы уверены, что хотите сгенерировать отчет для $limit пользователей? [y/N] ",
false
);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('<comment>Операция отменена.</comment>');
return Command::SUCCESS;
}
// Имитация получения данных из БД
$users = $this->fetchUsers($limit, $output, $isVerbose);
if (empty($users)) {
$output->writeln('<error>Нет пользователей для отчета.</error>');
return Command::FAILURE;
}
// Генерация отчета
$output->writeln('<info>Генерация отчета...</info>');
$reportContent = $this->generateReport($users, $format);
// Сохранение в файл
$fullPath = __DIR__ . '/../../var/reports/' . $filename;
if (!is_dir(dirname($fullPath))) {
mkdir(dirname($fullPath), 0777, true);
}
file_put_contents($fullPath, $reportContent);
$output->writeln("<info>Отчет сохранен в: $fullPath</info>");
$output->writeln("<info>Всего пользователей: " . count($users) . "</info>");
if ($isVerbose) {
$output->writeln('<comment>Подробная информация:</comment>');
foreach ($users as $user) {
$output->writeln(" - {$user['name']} ({$user['email']})");
}
}
return Command::SUCCESS;
}
private function fetchUsers(int $limit, OutputInterface $output, bool $verbose): array
{
$users = [];
$progressBar = new ProgressBar($output, $limit);
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%');
$progressBar->start();
for ($i = 1; $i <= $limit; $i++) {
// Симуляция задержки запроса к БД
usleep(20000);
$users[] = [
'id' => $i,
'name' => "User_$i",
'email' => "user$i@example.com",
'status' => $i % 3 === 0 ?