Symfony/Console для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 ?                    

Рекомендаций