Symfony Console
The Console component from Symfony framework. Makes it easy to create beautiful and testable command line interfaces.
GitHub Overview
symfony/console
Eases the creation of beautiful and testable command line interfaces
Topics
Star History
Framework
Symfony Console
Overview
The Symfony Console component eases the creation of beautiful and testable command-line interfaces. It provides features for creating commands, processing input arguments and options, outputting formatted text, and interacting with users through prompts and dialogues. It has become the de facto standard for PHP CLI applications and is used by many major PHP tools including Composer, PHPStan, and Behat. The latest stable version is 7.3.1, with the latest LTS version being 6.4.23, supporting PHP 8.1.0 and later.
Details
Symfony Console provides comprehensive functionality needed for CLI application development, including command-line argument parsing, help message generation, error handling, and output decoration. It can be used both as a standalone component in any PHP application and as part of the full Symfony framework.
Latest Updates (2024-2025)
- PHP 8.1.0+ Support: Leverages latest PHP features
- Continuous Feature Expansion: Addition of new helper components and output capabilities
- Runtime Component Integration: Integration with Symfony Runtime enables simpler application configuration
- PolyfillPhp85: Polyfill making PHP 8.5 features available on lower versions (March 2025 release)
- Console Completion: Support for automatic shell completion script generation since version 5.4
Key Features
- Command Definition: Command definition using
#[AsCommand]
attributes or class extension - Arguments and Options: Type-safe argument and option processing
- Automatic Help Generation: Automatic help message generation from command descriptions
- Output Decoration: Beautiful output with coloring and formatting
- Interactive Features: User input and dialog display capabilities
- Progress Display: Progress bars and progress indicators
- Table Output: Tabular display of structured data
- Tree Display: Visual display of hierarchical data
- Configuration Files: Externalization of application configuration
- Testing Support: Unit testing functionality for commands
Core Components
- Application: Main console application class
- Command: Base class for individual commands
- Input: Input processing for arguments and options
- Output: Output and format management
- Helper: Helper functions for tables, progress bars, etc.
- Style: Unified output styling with SymfonyStyle
Global Options
Options automatically available for all commands:
--verbose
(-v, -vv, -vvv): Sets verbosity level--silent
: Disables all output and interaction (added in Symfony 7.2)--quiet
: Disables output and interaction (errors still displayed)--no-interaction
: Disables interaction--version
: Displays version number--help
: Displays help--ansi
/--no-ansi
: Controls color output
Pros and Cons
Pros
- Rich Functionality: All necessary features for CLI development included
- High Adoption: Used by major tools like Composer, PHPStan, Behat
- Excellent Documentation: Comprehensive official documentation and tutorials
- Easy Testing: Simple unit testing for commands
- Extensibility: Custom helpers and styles can be created
- Modern PHP: Design leveraging latest PHP features
- Long-term Support: Stable support through LTS versions
- Runtime Integration: Simple configuration with Symfony Runtime
Cons
- PHP Dependency: PHP-only, cannot be used with other languages
- Learning Curve: Time required to understand all features due to rich functionality
- Performance: PHP startup time may impact small commands
- Dependencies: Some library dependencies even for standalone use
Key Links
Code Examples
Basic Command Creation
<?php
// src/Command/GreetCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
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;
#[AsCommand(
name: 'app:greet',
description: 'Greet the specified user',
hidden: false
)]
class GreetCommand extends Command
{
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'User name')
->addOption('yell', null, InputOption::VALUE_NONE, 'Display in uppercase')
->addOption('iterations', null, InputOption::VALUE_REQUIRED, 'Number of iterations', 1)
->setHelp('This command greets the specified user...');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$iterations = $input->getOption('iterations');
$yell = $input->getOption('yell');
$text = "Hello, {$name}!";
if ($yell) {
$text = strtoupper($text);
}
for ($i = 0; $i < $iterations; $i++) {
$output->writeln($text);
}
return Command::SUCCESS;
}
}
Application Setup
#!/usr/bin/env php
<?php
// bin/console
require __DIR__.'/../vendor/autoload.php';
use App\Command\GreetCommand;
use Symfony\Component\Console\Application;
$application = new Application('MyApp', '1.0.0');
$application->add(new GreetCommand());
$application->run();
Inline Command Definition
<?php
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
$application = new Application();
// Define command inline
$application->register('generate-admin')
->addArgument('username', InputArgument::REQUIRED, 'Username')
->addOption('email', null, InputOption::VALUE_REQUIRED, 'Email address')
->setCode(function (InputInterface $input, OutputInterface $output): int {
$username = $input->getArgument('username');
$email = $input->getOption('email');
$output->writeln("Creating admin user...");
$output->writeln("Username: {$username}");
if ($email) {
$output->writeln("Email: {$email}");
}
return Command::SUCCESS;
});
$application->run();
Beautiful Output with SymfonyStyle
<?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;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'app:report')]
class ReportCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// Title display
$io->title('System Report');
// Success message
$io->success('System is running normally');
// Info message
$io->info([
'Database connection: OK',
'Cache: Enabled',
'Log files: Writable'
]);
// Warning message
$io->warning('Some features are under maintenance');
// Error message
$io->error('Critical error detected');
// Table display
$io->table(
['ID', 'Username', 'Status'],
[
[1, 'john_doe', 'Active'],
[2, 'jane_smith', 'Suspended'],
[3, 'bob_wilson', 'Active']
]
);
// Progress bar
$io->progressStart(100);
for ($i = 0; $i < 100; $i++) {
usleep(50000); // 50ms wait
$io->progressAdvance();
}
$io->progressFinish();
return Command::SUCCESS;
}
}
Interactive Command
<?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;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'app:setup')]
class SetupCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$helper = $this->getHelper('question');
$io->title('Application Setup');
// Text input
$nameQuestion = new Question('Enter application name: ', 'MyApp');
$appName = $helper->ask($input, $output, $nameQuestion);
// Password input (hidden)
$passwordQuestion = new Question('Enter database password: ');
$passwordQuestion->setHidden(true);
$password = $helper->ask($input, $output, $passwordQuestion);
// Choice selection
$dbQuestion = new ChoiceQuestion(
'Select database',
['MySQL', 'PostgreSQL', 'SQLite'],
'MySQL'
);
$database = $helper->ask($input, $output, $dbQuestion);
// Confirmation question
$confirmQuestion = new ConfirmationQuestion(
'Execute setup? [y/N] ',
false
);
if (!$helper->ask($input, $output, $confirmQuestion)) {
$io->warning('Setup cancelled');
return Command::SUCCESS;
}
// Execute setup
$io->section('Configuration');
$io->definitionList(
['Application name' => $appName],
['Database' => $database],
['Password' => str_repeat('*', strlen($password))]
);
$io->success('Setup completed!');
return Command::SUCCESS;
}
}
Custom Helper Creation
<?php
namespace App\Helper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Output\OutputInterface;
class DatabaseHelper extends Helper
{
public function validateConnection(string $host, string $database, OutputInterface $output): bool
{
$output->writeln("Testing database connection...");
// Actual connection test logic
try {
// PDO connection test etc.
$output->writeln("<info>Database connection successful</info>");
return true;
} catch (\Exception $e) {
$output->writeln("<error>Database connection failed: {$e->getMessage()}</error>");
return false;
}
}
public function getName(): string
{
return 'database';
}
}
// Usage in command
class DatabaseCommand extends Command
{
protected function configure(): void
{
$this->getHelperSet()->set(new DatabaseHelper());
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$dbHelper = $this->getHelper('database');
$success = $dbHelper->validateConnection('localhost', 'myapp', $output);
return $success ? Command::SUCCESS : Command::FAILURE;
}
}
Advanced Output Control
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:advanced-output')]
class AdvancedOutputCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Define custom style
$alertStyle = new OutputFormatterStyle('red', '#ff0', ['bold', 'blink']);
$output->getFormatter()->setStyle('alert', $alertStyle);
// Color output
$output->writeln('<info>Info message</info>');
$output->writeln('<comment>Comment</comment>');
$output->writeln('<question>Question</question>');
$output->writeln('<error>Error message</error>');
$output->writeln('<alert>Custom alert</alert>');
// Section output
if ($output instanceof ConsoleOutputInterface) {
$section1 = $output->section();
$section2 = $output->section();
$section1->writeln('Section 1 content');
$section2->writeln('Section 2 content');
sleep(1);
// Overwrite section content
$section1->overwrite('Section 1 updated');
sleep(1);
// Clear section
$section2->clear();
}
// Output control by verbosity level
$output->writeln('Always displayed message');
$output->writeln('Verbose message', OutputInterface::VERBOSITY_VERBOSE);
$output->writeln('Very verbose message', OutputInterface::VERBOSITY_VERY_VERBOSE);
$output->writeln('Debug message', OutputInterface::VERBOSITY_DEBUG);
return Command::SUCCESS;
}
}
Testable Command
<?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:process-data')]
class ProcessDataCommand extends Command
{
public function __construct(
private readonly DataProcessor $dataProcessor
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Starting data processing...');
try {
$result = $this->dataProcessor->process();
$output->writeln("Processing complete: {$result['count']} records processed");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Error: {$e->getMessage()}</error>");
return Command::FAILURE;
}
}
}
// PHPUnit test
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
class ProcessDataCommandTest extends TestCase
{
public function testExecute(): void
{
// Create mock
$dataProcessor = $this->createMock(DataProcessor::class);
$dataProcessor->method('process')
->willReturn(['count' => 5]);
// Test command
$command = new ProcessDataCommand($dataProcessor);
$application = new Application();
$application->add($command);
$commandTester = new CommandTester($command);
$commandTester->execute([]);
$this->assertSame(Command::SUCCESS, $commandTester->getStatusCode());
$this->assertStringContainsString('5 records', $commandTester->getDisplay());
}
}
Integration with Symfony Runtime
#!/usr/bin/env php
<?php
// bin/console
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): Application {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
// Returning an Application makes the Runtime run a Console
// application instead of the HTTP Kernel
return new Application($kernel);
};
Completion Feature Implementation
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:user-info')]
class UserInfoCommand extends Command
{
protected function configure(): void
{
$this->addArgument(
'username',
InputArgument::REQUIRED,
'Username',
null,
// Completion callback
function (CompletionInput $input): array {
// Value being typed by user
$currentValue = $input->getCompletionValue();
// Get list of usernames from database or API
$availableUsernames = $this->getUsernames($currentValue);
return $availableUsernames;
}
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$username = $input->getArgument('username');
$output->writeln("Displaying user info: {$username}");
return Command::SUCCESS;
}
private function getUsernames(string $currentValue): array
{
// In actual implementation, fetch from database or API
$allUsernames = ['alice', 'bob', 'charlie', 'david'];
// Filter by partial match
return array_filter($allUsernames, function($username) use ($currentValue) {
return strpos($username, $currentValue) === 0;
});
}
}