symfony/console
Symfony Console makes it easy to build beautiful, testable PHP command‑line tools. Define commands, arguments and options, add interactive prompts, styled output and progress bars, plus robust input/output handling for CLI apps and scripts.
Installation:
composer require symfony/console
For Laravel, this is already included via illuminate/console (which wraps Symfony/Console).
First Command:
Create a basic command in app/Console/Commands/ (Laravel convention):
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
class ExampleCommand extends Command
{
protected $signature = 'example:greet {name?} {--yell}';
protected $description = 'A simple greeting command';
public function handle()
{
$name = $this->argument('name') ?: 'World';
$output = $name . ($this->option('yell') ? '!!!' : '.');
$this->info($output);
}
}
Register it in app/Console/Kernel.php:
protected $commands = [
Commands\ExampleCommand::class,
];
Run It:
php artisan example:greet John --yell
Output:
[OK] John!!!
vendor/laravel/framework/src/Illuminate/Console/ for Laravel-specific extensions.php artisan db:backup instead of a cron job calling mysqldump).$signature (e.g., {name?} for optional args, --yell for flags).InputOption for complex options (e.g., --limit=<value>).
$this->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Max items', 100);
$this->validateArguments().$this->output methods (info(), comment(), error()) with verbosity awareness:
if ($this->output->isVerbose()) {
$this->output->writeln('Debug details...');
}
$progressBar = $this->output->createProgressBar(100);
for ($i = 0; $i < 100; $i++) {
$progressBar->advance();
}
$progressBar->finish();
$rows = [['ID', 'Name'], [1, 'John']];
$this->output->writeln('<comment>Users:</comment>');
$this->output->writeln($this->formatTable($rows));
Symfony\Component\Console\Style\SymfonyStyle for user interaction:
$io = new SymfonyStyle($this->input, $this->output);
$name = $io->ask('What is your name?', 'World');
$confirm = $io->confirm('Proceed?', false);
$choice = $io->choice('Pick one', ['A', 'B', 'C'], 'A');
use Symfony\Component\Console\Tester\CommandTester;
public function testExampleCommand()
{
$command = new ExampleCommand();
$commandTester = new CommandTester($command);
$commandTester->execute(['name' => 'Alice']);
$this->assertStringContainsString('Alice.', $commandTester->getDisplay());
}
ApplicationTester:
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Tester\ApplicationTester;
public function testArtisanCommand()
{
$kernel = bootstrap_app();
$application = new Application($kernel);
$tester = new ApplicationTester($application);
$tester->run(['example:greet', 'Bob']);
$this->assertSame('Bob.', trim($tester->getDisplay()));
}
event(new CommandStarting($this->getName()));
// ... command logic ...
event(new CommandCompleted($this->getName(), true));
EventServiceProvider:
protected $listen = [
'command.starting' => [
Handlers\LogCommandStart::class,
],
];
// app/Console/Commands/UserCommand.php
protected $signature = 'user:{action}';
protected $description = 'User management commands';
// app/Console/Commands/User/ListCommand.php
protected $signature = 'user:list';
Kernel.php:
protected $commands = [
Commands\UserCommand::class,
];
php artisan vendor:publish --tag=artisan-commands
Bus for decoupled logic:
$this->dispatch(new ProcessBackupJob($path));
app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('example:greet John --yell')
->dailyAt('09:00');
}
Verbosity Confusion:
$this->output->isVerbose() or $this->output->isDebug() to gate output:
if ($this->output->isVeryVerbose()) {
$this->output->writeln('Detailed debug info...');
}
OutputInterface::VERBOSITY_NORMAL.Signal Handling:
Ctrl+C (SIGINT).$this->input->getSignalHandler()->attach(
function () use ($progressBar) {
$progressBar->finish();
$this->output->writeln('<error>Cancelled!</error>');
exit(1);
},
SIGINT
);
$this->input->getSignalHandler()->attach(fn() => exit(1), SIGINT);.Windows Line Endings:
\r\n line endings break progress bars or tables.$this->output->setDecorated(false); // Disable ANSI colors if needed
str_replace("\r\n", "\n", $output) for raw strings.Argument Parsing Quirks:
# (e.g., --limit=10#) are treated as null.# in signatures or use InputOption::VALUE_IS_ARRAY.Interactive Mode in Tests:
ApplicationTester ignores interactive mode.$tester->setInput('test');
$tester->execute(['command:name'], ['interactive' => false]);
$this->output->writeln('Input: ' . print_r($this->input->getArguments(), true));
php artisan command:name --verbose
$this->output->setVerbosity(self::VERBOSITY_DEBUG);
$this->output->setDecorated(false);
file_put_contents('debug.log', $this->output->getContent());
if (!$this->output->isDecorated()) {
$this->output->writeln('Skipping progress bar (non-interactive)');
How can I help you explore Laravel packages today?