symfony/console
The Console component eases the creation of beautiful and testable command line interfaces for PHP applications, with support for input/output handling, styling, and argument parsing.
Minimal setup for a Laravel app
Check the installed version first:
composer show symfony/console
Laravel already depends on symfony/console through Artisan; you normally should not run composer require symfony/console in a Laravel application unless you are building a standalone console app. If your app is locked to the Symfony Console 5.4 LTS line, update to at least v5.4.54 because of the security patch that escapes context strings written to the terminal (#65692). For newer Symfony branches, use the corresponding patched release.
Create a command:
php artisan make:command GreetCommand
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class GreetCommand extends Command
{
protected $signature = 'test:greet {name?}';
protected $description = 'Display a greeting';
public function handle(): int
{
$name = $this->argument('name') ?: 'World';
$this->info("Hello, {$name}!");
return self::SUCCESS;
}
}
Registration:
app/Console/Commands.app/Console/Kernel.php via $commands, or rely on the framework’s command discovery depending on version.Run it:
php artisan test:greet Laravel
Where to look first
Command, Application, InputInterface, OutputInterface, SymfonyStyle.Illuminate\Console\CommandSymfony\Component\Console\Input\InputInterfaceSymfony\Component\Console\Output\OutputInterfaceSymfony\Component\Console\Style\SymfonyStyleFirst useful use case
Start with a small operational command: accept one or two arguments/options, perform a service-layer action, print a clear result, and return a meaningful exit code. This keeps commands thin and testable.
Define arguments and options in $signature, then read them in handle():
protected $signature = 'user:create
{name : The user name}
{email : The user email}
{--admin : Create as administrator}';
public function handle(): int
{
$name = $this->argument('name');
$email = $this->argument('email');
$isAdmin = (bool) $this->option('admin');
// Delegate to a service/action class...
return self::SUCCESS;
}
Use $this->argument() and $this->option() rather than reading $_SERVER['argv'] directly.
Laravel commands do not have a built-in validateArguments() hook. Validate inside handle() or delegate to Laravel’s validator/service layer:
$validator = validator(
['email' => $this->argument('email')],
['email' => ['required', 'email']]
);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $error) {
$this->error($error);
}
return self::FAILURE;
}
Interactive helpers:
if (! $this->confirm('Proceed?', false)) {
return self::SUCCESS;
}
$name = $this->ask('Your name');
$password = $this->secret('Password');
$environment = $this->choice('Environment', ['dev', 'staging', 'prod'], 'dev');
Guard interactive prompts when commands may run in CI or queued/scheduled contexts:
if (! $this->input->isInteractive()) {
// Use defaults or fail fast.
}
Prefer Laravel’s output helpers so tests can assert output:
$this->info('Success!');
$this->error('Failed!');
$this->warn('Warning');
$this->line('Plain text');
$this->comment('Hint');
Tables:
$this->table(
['Name', 'Email'],
[
['Laravel', '[email protected]'],
]
);
Progress bars:
$bar = $this->output->createProgressBar($total);
foreach ($items as $item) {
// Process item...
$bar->advance();
}
$bar->finish();
$this->newLine(2);
Illuminate\Console\OutputStyle extends Symfony’s SymfonyStyle, so richer section/title helpers are also available through $this->output when needed.
Inside a command:
$this->call('cache:clear');
Programmatically:
use Illuminate\Support\Facades\Artisan;
Artisan::call('cache:clear');
$output = Artisan::output();
Keep cross-command orchestration shallow; prefer service classes for shared logic.
Laravel’s Artisan test helpers are usually the best fit:
public function test_greet_command(): void
{
$this->artisan('test:greet', ['name' => 'Laravel'])
->expectsOutput('Hello, Laravel!')
->assertExitCode(0);
}
For interactive commands:
$this->artisan('user:create')
->expectsQuestion('Your name', 'Taylor')
->expectsQuestion('Password', 'secret')
->assertExitCode(0);
Symfony\Component\Console\Tester\CommandTester is useful for standalone Symfony Console commands, but Laravel command tests are usually simpler with $this->artisan().
Symfony Console emits console events such as ConsoleEvents::COMMAND, TERMINATE, and ERROR. In Laravel, register listeners in a service provider:
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Illuminate\Support\Facades\Event;
Event::listen(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
// Inspect or adjust the command/input/output.
});
For graceful shutdown behavior, implement Symfony’s SignalableCommandInterface instead of inventing a custom signal hook.
The v5.4.54 security release escapes context strings written to the terminal (#65692). This matters when user-controlled data, exception messages, or external input can contain terminal escape sequences.
>=5.4.54.echo or unescaped writeln() with untrusted strings.use Symfony\Component\Console\Formatter\OutputFormatter;
$this->line(OutputFormatter::escape($userInput));
app/Console/Kernel.php; commands in app/Console/Commands are auto-discovered.app/Console/Kernel.php and $commands.symfony/console to a Laravel app unless you have a specific standalone-console need. Let Laravel’s dependency constraints manage it.Avoid mixing direct echo/print with Artisan output. Direct writes bypass OutputInterface, break output styling, and make expectsOutput() assertions unreliable. Use $this->line(), $this->info(), $this->output->write(), etc.
Prompts can hang or fail in non-interactive environments. Check $this->input->isInteractive(), provide sensible defaults, and run CI commands with --no-interaction where appropriate.
Return an integer exit code instead of void:
return self::SUCCESS;
return self::FAILURE;
This makes commands friendlier to schedulers, shell scripts, and tests.
Finish progress bars and add a newline before printing more output. Mixing progress bars with sections or arbitrary writes can produce messy output. Use $bar->clear() before important messages if needed.
Use verbosity flags:
php artisan test:greet Laravel -v
php artisan test:greet Laravel -vvv
Inside code:
$this->output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
Useful checks:
php artisan list to inspect registered commands.php artisan help test:greet to inspect signature/options.Artisan::output() after Artisan::call() in tests or debugging code.handle() or the command constructor.SignalableCommandInterface for signal-aware commands.Application, CommandTester, and HelperSet directly; inside Laravel, prefer Artisan’s command and testing helpers.How can I help you explore Laravel packages today?