Console Commands
The Symfony framework provides lots of commands through the bin/console script
(e.g. the well-known bin/console cache:clear command). These commands are
created with the Console component. You can also use it to create your own
commands, both in Symfony applications and in any other PHP project.
Installation
Symfony applications include the Console component by default. In other PHP projects, install it with Composer:
1
$ composer require symfony/console
Running Commands
Each Symfony application comes with a large set of commands. You can use
the list command to view all available commands in the application:
1 2 3 4 5 6 7 8 9 10 11 12 13
$ php bin/console list
...
Available commands:
about Display information about the current project
completion Dump the shell completion script
help Display help for a command
list List commands
assets
assets:install Install bundle's web assets under a public directory
cache
cache:clear Clear the cache
...
Note
list is the default command, so running php bin/console is the same.
If you find the command you need, you can run it with the --help option
to view the command's documentation:
1
$ php bin/console assets:install --help
Note
--help is one of the built-in global options from the Console component,
which are available for all commands, including those you can create.
To learn more about them, you can read
this section.
Shortcut Syntax
You don't have to type the full command names. You can type the shortest
unambiguous name to run a command. When command names use : to define
namespaces, you only need to type the shortest unambiguous text for each part:
1 2 3 4 5
# runs the "cache:clear" command
$ php bin/console ca:cl
# as long as it's unambiguous, you can also mix upper and lower case
$ php bin/console Ca:Cl
If the short name is ambiguous (i.e. more than one command matches it), no command is run and Symfony displays the matching commands so you can choose one of them.
APP_ENV & APP_DEBUG
Console commands run in the environment defined in the APP_ENV
variable of the .env file, which is dev by default. It also reads the APP_DEBUG
value to turn "debug" mode on or off (it defaults to 1, which is on).
To run the command in another environment or debug mode, edit the value of APP_ENV
and APP_DEBUG. You can also define these env vars when running the
command, for instance:
1 2
# clears the cache for the prod environment
$ APP_ENV=prod php bin/console cache:clear
Console Completion
If you are using the Bash, Zsh or Fish shell, you can install Symfony's completion script to get auto completion when typing commands in the terminal. All commands support name and option completion, and some can even complete values.
First, you have to install the completion script once. Run
bin/console completion --help for the installation instructions for
your shell.
Note
When using Bash, make sure you installed and setup the "bash completion"
package for your OS (typically named bash-completion).
After installing and restarting your terminal, you're all set to use completion (by default, by pressing the Tab key).
Tip
Many PHP tools are built using the Symfony Console component (e.g. Composer, PHPStan and Behat). If they are using version 5.4 or higher, you can also install their completion script to enable console completion:
1 2
$ php vendor/bin/phpstan completion --help
$ composer completion --help
Tip
If you are using the Symfony CLI tool, follow these instructions to enable autocompletion.
Creating a Command
Commands are defined in classes and auto-registered using the #[AsCommand]
attribute. For example, you may want a command to create a user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// src/Command/CreateUserCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
// the name of the command is what users type after "php bin/console"
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __invoke(): int
{
// ... put here the code to create the user
// this method must return an integer number with the "exit status code"
// of the command. You can also use these constants to make code more readable
// return this if there was no problem running the command
// (it's equivalent to returning int(0))
return Command::SUCCESS;
// or return this if some error happened during the execution
// (it's equivalent to returning int(1))
// return Command::FAILURE;
// or return this to indicate incorrect command usage; e.g. invalid options
// or missing arguments (it's equivalent to returning int(2))
// return Command::INVALID
}
}
You can also use #[AsCommand] to add a description, usage examples, and
longer help text for the command:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#[AsCommand(
name: 'app:create-user',
// this short description is shown when running "php bin/console list"
description: 'Creates a new user.',
// this is shown when running the command with the "--help" option
help: 'This command allows you to create a user...',
// this allows you to show one or more usage examples (no need to add the command name)
usages: ['bob', 'alice --as-admin'],
)]
class CreateUserCommand
{
public function __invoke(): int
{
// ...
}
}
Additionally, you can extend the Command class to leverage advanced features like lifecycle hooks (e.g. initialize() and interact()):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
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:create-user')]
class CreateUserCommand extends Command
{
public function initialize(InputInterface $input, OutputInterface $output): void
{
// ...
}
public function interact(InputInterface $input, OutputInterface $output): void
{
// ...
}
public function __invoke(): int
{
// ...
}
}
Method-based Commands
8.1
Support for method-based console commands was introduced in Symfony 8.1.
Instead of creating one class per command, you can define multiple commands
in the same class by adding the #[AsCommand] attribute to individual
public methods. This is useful to group related commands that share the
same dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
class UserCommands
{
#[AsCommand('app:user:create')]
public function create(OutputInterface $output): int
{
// ...
return Command::SUCCESS;
}
#[AsCommand('app:user:delete')]
public function delete(OutputInterface $output): int
{
// ...
return Command::SUCCESS;
}
}
Each annotated method becomes an independent command that can be run, listed and tested separately.
Note
When using the Console component without the service container, you can register method-based commands manually:
1 2 3 4 5 6
$commands = new UserCommands($userRepository);
$application = new Application();
// the "..." is PHP's first-class callable syntax, which turns each method into a callable
$application->addCommand($commands->create(...));
$application->addCommand($commands->delete(...));
Registering the Command
In Symfony applications, commands are registered automatically. If you're using
the default services.yaml configuration,
your command classes are already registered as services and Symfony finds them
thanks to the #[AsCommand] attribute and autoconfiguration.
If you can't use PHP attributes, register the command as a service and
tag it with the console.command tag.
8.1
Registering console commands by overriding the Bundle::registerCommands()
method is deprecated. Use the #[AsCommand] attribute or the
console.command service tag instead.
Commands are loaded lazily: their classes are only instantiated when the
command is run. This is possible because Symfony reads the command name from
the #[AsCommand] attribute without instantiating the class. When registering
commands manually with the console.command tag, set the command name in the
command attribute of the tag to get the same behavior:
1 2 3 4 5 6 7
# config/services.yaml
services:
# ...
App\Command\CreateUserCommand:
tags:
- { name: 'console.command', command: 'app:create-user' }
Warning
Running the list command instantiates all commands, including lazy ones.
The only exception is commands defined as
LazyCommand instances, whose command factory
is not executed.
In standalone applications, create the console application in a PHP script and register your commands with the addCommand() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/usr/bin/env php
<?php
// application.php
require __DIR__.'/vendor/autoload.php';
use App\Command\CreateUserCommand;
use Symfony\Component\Console\Application;
// the name and version are optional; they are displayed when running "--version"
$application = new Application('Acme Console Application', '1.2');
$application->addCommand(new CreateUserCommand());
// ... register other commands
$application->run();
You can also define commands inline with a closure, without creating a class.
Use the register() method
and pass the closure to setCode(). The closure accepts the same
#[Argument] and #[Option] parameters as the __invoke() method:
1 2 3 4 5 6 7 8 9 10 11
// ...
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
$application->register('app:create-user')
->setCode(function (#[Argument] string $username, OutputInterface $output): int {
// ...
return Command::SUCCESS;
});
Running the Command
After configuring and registering the command, you can run it in the terminal:
1 2 3 4
$ php bin/console app:create-user
# in standalone applications, run the script that creates the application
$ php application.php app:create-user
As you might expect, this command will do nothing as you didn't write any logic
yet. Add your own logic inside the __invoke() method.
Command Aliases
You can define alternative names (aliases) for a command directly in its name
using a pipe (|) separator. The first name in the list becomes the actual
command name; the others are aliases that can also be used to run the command:
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Command/CreateUserCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(
name: 'app:create-user|app:add-user|app:new-user',
description: 'Creates a new user.',
)]
class CreateUserCommand
{
// ...
}
Hiding Commands
By default, all commands are listed when running the list command or when
running the console application without arguments. However, some commands are
not intended to be run by end-users; for example, commands for the legacy parts
of the application or commands only run by scheduled tasks.
In those cases, define the command as hidden by setting the hidden
option of the #[AsCommand] attribute to true:
1 2 3 4 5 6 7 8 9 10
// src/Command/LegacyCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'app:legacy', hidden: true)]
class LegacyCommand
{
// ...
}
You can also hide a command using the pipe (|) syntax of command aliases:
use the command name as one of the aliases and leave the main command name (the
part before the |) empty:
1 2 3 4 5 6 7 8 9 10
// src/Command/LegacyCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: '|app:legacy')]
class LegacyCommand
{
// ...
}
Note
Hidden commands are still available using the JSON or XML descriptor.
Console Output
The __invoke() method has access to the output stream to write messages to
the console:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// ...
public function __invoke(OutputInterface $output): int
{
// outputs multiple lines to the console (adding "\n" at the end of each line)
$output->writeln([
'User Creator',
'============',
'',
]);
// the value returned by someMethod() can be an iterator (https://php.net/iterator)
// that generates and returns the messages with the 'yield' PHP keyword
$output->writeln($this->someMethod());
// outputs a message followed by a "\n"
$output->writeln('Whoa!');
// outputs a message without adding a "\n" at the end of the line
$output->write('You are about to ');
$output->write('create a user.');
return Command::SUCCESS;
}
Now, try executing the command:
1 2 3 4 5 6
$ php bin/console app:create-user
User Creator
============
Whoa!
You are about to create a user.
Output Sections
The regular console output can be divided into multiple independent regions called "output sections". Create one or more of these sections when you need to clear and overwrite the output information.
Sections are created with the ConsoleOutput::section() method, which returns an instance of ConsoleSectionOutput:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// ...
use Symfony\Component\Console\Output\ConsoleOutputInterface;
#[AsCommand(name: 'app:my-command')]
class MyCommand
{
public function __invoke(OutputInterface $output): int
{
if (!$output instanceof ConsoleOutputInterface) {
throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".');
}
$section1 = $output->section();
$section2 = $output->section();
$section1->writeln('Hello');
$section2->writeln('World!');
sleep(1);
// Output displays "Hello\nWorld!\n"
// overwrite() replaces all the existing section contents with the given content
$section1->overwrite('Goodbye');
sleep(1);
// Output now displays "Goodbye\nWorld!\n"
// clear() deletes all the section contents...
$section2->clear();
sleep(1);
// Output now displays "Goodbye\n"
// ...but you can also delete a given number of lines
// (this example deletes the last two lines of the section)
$section1->clear(2);
sleep(1);
// Output is now completely empty!
// setting the max height of a section will make new lines replace the old ones
$section1->setMaxHeight(2);
$section1->writeln('Line1');
$section1->writeln('Line2');
$section1->writeln('Line3');
return Command::SUCCESS;
}
}
Note
A new line is appended automatically when displaying information in a section.
Output sections let you manipulate the Console output in advanced ways, such as displaying multiple progress bars which are updated independently and appending rows to tables that have already been rendered.
Warning
Terminals only allow overwriting the visible content, so you must take into account the console height when trying to write/overwrite section contents.
Console Input
Use input options or arguments to pass information to the command:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use Symfony\Component\Console\Attribute\Argument;
// The #[Argument] attribute configures $username as a
// required input argument and its value is automatically
// passed to this parameter
public function __invoke(#[Argument('The username of the user.')] string $username, OutputInterface $output): int
{
$output->writeln([
'User Creator',
'============',
'',
]);
$output->writeln('Username: '.$username);
return Command::SUCCESS;
}
Now, you can pass the username to the command:
1 2 3 4 5
$ php bin/console app:create-user Wouter
User Creator
============
Username: Wouter
See also
Read Console Input (Arguments & Options) for more information about console options and arguments.
Getting Services from the Service Container
To actually create a new user, the command has to access some
services. Since your command is already registered
as a service, you can use normal dependency injection. Imagine you have a
App\Service\UserManager service that you want to access:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// ...
use App\Service\UserManager;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
public function __construct(
private UserManager $userManager
) {
}
public function __invoke(#[Argument] string $username, OutputInterface $output): int
{
// ...
$this->userManager->create($username);
$output->writeln('User successfully generated!');
return Command::SUCCESS;
}
}
Note
Standalone applications don't have a service container, so pass the
dependencies yourself when registering the command:
$application->addCommand(new CreateUserCommand($userManager)).
See also
Read Console Argument Value Resolvers for more information about advanced
service injection features (such as #[Autowire], #[Target], and
custom value resolvers).
Command Lifecycle
Commands have three lifecycle methods that are invoked when running the command:
- initialize() (optional)
-
This method is executed before the
interact()and theexecute()methods. Its main purpose is to initialize variables used in the rest of the command methods. - interact() (optional)
-
This method is executed after
initialize()and beforeexecute(). Its purpose is to check if some of the options/arguments are missing and interactively ask the user for those values. This is the last place where you can ask for missing required options/arguments. This method is called before validating the input. Note that it will not be called when the command is run without interaction (e.g. when passing the--no-interactionglobal option flag). __invoke()(or execute()) (required)-
This method is executed after
interact()andinitialize(). It contains the logic you want the command to execute and it must return an integer which will be used as the command exit status.
Testing Commands
In test classes extending KernelTestCase,
Symfony provides the runCommand() method to run console commands and inspect
their results. Some assertions are also provided by the ConsoleCommandAssertionsTrait
to check the command execution status:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// tests/Command/CreateUserCommandTest.php
namespace App\Tests\Command;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CreateUserCommandTest extends KernelTestCase
{
public function testExecute(): void
{
$result = static::runCommand('app:create-user', [
// pass arguments to the helper
'username' => 'Wouter',
// prefix the key with two dashes when passing options,
// e.g: '--some-option' => 'option_value',
// use brackets for testing array value,
// e.g: '--some-option' => ['option_value'],
// use true for options that accept no value (InputOption::VALUE_NONE),
// e.g: '--some-option' => true,
]);
$this->assertCommandIsSuccessful($result);
// you can also check for a failed or invalid command:
// $this->assertCommandFailed($result);
// $this->assertCommandIsInvalid($result);
// the output of the command in the console
$output = $result->getOutput();
$this->assertStringContainsString('Username: Wouter', $output);
// ...
}
}
8.1
The runCommand() method and the ExecutionResult class were
introduced in Symfony 8.1.
The ExecutionResult object gives access to stdout, stderr and the combined
display separately:
1 2 3 4 5 6 7 8 9 10 11
// stdout only
$result->getOutput();
// stderr only
$result->getErrorOutput();
// the combined output (stdout + stderr interleaved)
$result->getDisplay();
// the exit code
$result->statusCode;
You can also assert multiple expectations at once using assertCommandResultEquals():
1 2 3 4 5
$this->assertCommandResultEquals(
$result,
expectedStatusCode: 0,
expectedOutput: 'User "Wouter" was created.',
);
If your command requires interactive inputs, pass them as the third argument:
1
$result = static::runCommand('app:create-user', [], ['Wouter', 'yes']);
Tip
You can also test a whole console application by using ApplicationTester. When using it (or when testing a single-command application), disable the auto exit flag:
1 2 3 4
$application = new Application();
$application->setAutoExit(false);
$tester = new ApplicationTester($application);
Warning
When testing commands using the CommandTester class, console events are
not dispatched. If you need to test those events, use the
ApplicationTester instead.
Note
When using the Console component in a standalone project (without the
Symfony framework), extend \PHPUnit\Framework\TestCase instead of
KernelTestCase and use
Application instead of the
FrameworkBundle one.
Testing Console Applications
In addition to testing single commands, you can test full console applications with the ApplicationTester class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\ApplicationTester;
class WelcomeCommandTest extends KernelTestCase
{
public function testPerson(): void
{
self::bootKernel();
$application = new Application(self::$kernel);
// this is key: don't terminate the PHP process after running the command
$application->setAutoExit(false);
$applicationTester = new ApplicationTester($application);
$applicationTester->run([
'command' => 'app:welcome-person',
'firstName' => 'Jane',
'lastName' => 'Smith',
'hobbies' => ['reading', 'dancing']
]);
$applicationTester->assertCommandIsSuccessful();
$output = $applicationTester->getDisplay();
$this->assertStringContainsString('Jane Smith', $output);
$this->assertStringContainsString('reading and dancing', $output);
}
}
Note
Don't forget to call setAutoExit(false) on the application before passing
it to ApplicationTester. Without it, the application calls exit()
after running the command, which would terminate the PHPUnit process.
Legacy Command Tester
Using the runCommand() method from KernelTestCase is the recommended way
of testing commands in Symfony applications. However, you can also use the
CommandTester class directly,
which is useful when testing commands outside the Symfony framework or when
you need lower-level control over the testing setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// tests/Command/CreateUserCommandTest.php
namespace App\Tests\Command;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
class CreateUserCommandTest extends KernelTestCase
{
public function testExecute(): void
{
self::bootKernel();
$application = new Application(self::$kernel);
$command = $application->find('app:create-user');
$commandTester = new CommandTester($command);
$commandTester->execute([
// pass arguments to the helper
'username' => 'Wouter',
// prefix the key with two dashes when passing options,
// e.g: '--some-option' => 'option_value',
// use brackets for testing array value,
// e.g: '--some-option' => ['option_value'],
// use true for options that accept no value (InputOption::VALUE_NONE),
// e.g: '--some-option' => true,
]);
$commandTester->assertCommandIsSuccessful();
// the output of the command in the console
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Username: Wouter', $output);
// ...
}
}
Note
If you are using a single-command application,
call setAutoExit(false) on the application to get the command result in CommandTester.
Tip
Use PHP's first-class callable syntax to test method-based commands:
1 2 3 4
$commands = new UserCommands($userRepository);
$tester = new CommandTester($commands->create(...));
$tester->execute([]);
Warning
When testing InputOption::VALUE_NONE command options, you must pass true
to them:
1 2
$commandTester = new CommandTester($command);
$commandTester->execute(['--some-option' => true]);
Note
The CommandTester class does not implement ConsoleOutputInterface,
so methods like section() are not directly accessible. To test them,
use the capture_stderr_separately option of the execute() method:
1
$commandTester->execute([], ['capture_stderr_separately' => true]);
Getting Terminal Information
When testing your commands, it could be useful to understand how your command reacts on different settings like the width and the height of the terminal, or even the color mode being used. You have access to such information thanks to the Terminal class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Component\Console\Terminal;
$terminal = new Terminal();
// gets the number of lines available
$height = $terminal->getHeight();
// gets the number of columns available
$width = $terminal->getWidth();
// gets the color mode
$colorMode = $terminal->getColorMode();
// changes the color mode
$colorMode = $terminal->setColorMode(AnsiColorMode::Ansi24);
Logging Command Errors
Whenever an exception is thrown while running commands, Symfony adds a log
message for it including the entire failing command. In addition, Symfony
registers an event subscriber to listen to the
ConsoleEvents::TERMINATE event and adds a log
message whenever a command doesn't finish with the 0 exit status.
Using the Console Logger
In Symfony applications, commands log messages by injecting the LoggerInterface
service. Monolog's console handler then displays
those messages in the console output according to the verbosity level.
Standalone applications and commands that don't use Monolog can use the
ConsoleLogger class instead. It's a
lightweight logger that complies with the PSR-3 standard and its only
dependency is psr/log. Depending on the verbosity level, it sends the log
messages to the OutputInterface
instance passed to its constructor. Consider a service that requires a PSR-3 logger:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
namespace App\Service;
use Psr\Log\LoggerInterface;
class DataImporter
{
public function __construct(
private LoggerInterface $logger,
) {
}
public function import(string $file): void
{
$this->logger->info('Importing data from '.$file);
// ...
}
}
Create a ConsoleLogger inside the command and pass it to the service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
namespace App\Command;
use App\Service\DataImporter;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'app:import-data',
description: 'Imports data from the given file'
)]
class ImportDataCommand
{
public function __invoke(#[Argument] string $file, OutputInterface $output): int
{
$logger = new ConsoleLogger($output);
$importer = new DataImporter($logger);
$importer->import($file);
return Command::SUCCESS;
}
}
The log messages emitted by the service are displayed in the console output.
Verbosity
Depending on the verbosity level that the command is run, messages may or may not be sent to the OutputInterface instance.
By default, the console logger behaves like the Monolog's Console Handler. The association between the log level and the verbosity can be configured through the second parameter of the ConsoleLogger constructor:
1 2 3 4 5 6 7 8 9
use Psr\Log\LogLevel;
// ...
$verbosityLevelMap = [
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL,
];
$logger = new ConsoleLogger($output, $verbosityLevelMap);
Color
The logger outputs the log messages formatted with a color reflecting their level. This behavior is configurable through the third parameter of the constructor:
1 2 3 4 5 6 7
// ...
$formatLevelMap = [
LogLevel::CRITICAL => ConsoleLogger::ERROR,
LogLevel::DEBUG => ConsoleLogger::INFO,
];
$logger = new ConsoleLogger($output, [], $formatLevelMap);
Errors
The console logger includes a hasErrored() method which returns true as
soon as any error message has been logged during the execution of the command.
This is useful to decide which status code to return as the result of executing
the command.
Using Console Events
The console application dispatches several events during the lifecycle of a command. Listen to them to run some logic before or after any command, to handle errors globally, etc. Console events are dispatched with the EventDispatcher component, so their listeners are regular event listeners:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/EventListener/BeforeCommandListener.php
namespace App\EventListener;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
// console events are dispatched with the names defined in the
// ConsoleEvents class, so the "event" argument is required
#[AsEventListener(event: ConsoleEvents::COMMAND)]
class BeforeCommandListener
{
public function __invoke(ConsoleCommandEvent $event): void
{
// ...
}
}
Warning
Console events are only triggered by the main command being executed. Commands called by the main command will not trigger any event, unless run by the application itself, see How to Call Other Commands.
The following sections explain each console event and show the __invoke()
method of a listener for it.
The ConsoleEvents::COMMAND Event
Typical Purposes: Doing something before any command is run (like logging which command is going to be executed), or displaying something about the event to be executed.
Just before executing any command, the ConsoleEvents::COMMAND event is
dispatched. Listeners receive a
ConsoleCommandEvent event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\Console\Event\ConsoleCommandEvent;
// ...
public function __invoke(ConsoleCommandEvent $event): void
{
// gets the input instance
$input = $event->getInput();
// gets the output instance
$output = $event->getOutput();
// gets the command to be executed
$command = $event->getCommand();
// writes something about the command
$output->writeln(sprintf('Before running command <info>%s</info>', $command->getName()));
// gets the application
$application = $command->getApplication();
}
Note
The command binds the input again before running, so any argument or
option value that you set in a listener with setArgument() or
setOption() is discarded. If you need to customize the input, change
the command definition instead. For example, add an option with
$command->addOption() and the command will parse it when it runs.
Using the
disableCommand()
method, you can disable a command inside a listener. The application
will then not execute the command, but instead will return the code 113
(defined in ConsoleCommandEvent::RETURN_CODE_DISABLED). This code is one
of the reserved exit codes for console commands that conform with the
C/C++ standard:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Console\Event\ConsoleCommandEvent;
// ...
public function __invoke(ConsoleCommandEvent $event): void
{
// gets the command to be executed
$command = $event->getCommand();
// ... check if the command can be executed
// disables the command, this will result in the command being skipped
// and code 113 being returned from the Application
$event->disableCommand();
// it is possible to enable the command in a later listener
if (!$event->commandShouldRun()) {
$event->enableCommand();
}
}
The ConsoleEvents::ERROR Event
Typical Purposes: Handle exceptions thrown during the execution of a command.
Whenever an exception is thrown by a command, including those triggered from
event listeners, the ConsoleEvents::ERROR event is dispatched. A listener
can wrap or change the exception or do anything useful before the exception is
thrown by the application.
Listeners receive a ConsoleErrorEvent event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use Symfony\Component\Console\Event\ConsoleErrorEvent;
// ...
public function __invoke(ConsoleErrorEvent $event): void
{
$output = $event->getOutput();
$command = $event->getCommand();
$output->writeln(sprintf('Oops, exception thrown while running command <info>%s</info>', $command->getName()));
// gets the current exit code (the exception code)
$exitCode = $event->getExitCode();
// changes the exception to another one
$event->setError(new \LogicException('Caught exception', $exitCode, $event->getError()));
}
The ConsoleEvents::TERMINATE Event
Typical Purposes: To perform some cleanup actions after the command has been executed.
After the command has been executed, the ConsoleEvents::TERMINATE event is
dispatched. It can be used to do any actions that need to be executed for all
commands or to cleanup what you initiated in a ConsoleEvents::COMMAND
listener (like sending logs, closing a database connection, sending emails,
...). A listener might also change the exit code.
Listeners receive a ConsoleTerminateEvent event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
// ...
public function __invoke(ConsoleTerminateEvent $event): void
{
// gets the output
$output = $event->getOutput();
// gets the command that has been executed
$command = $event->getCommand();
// displays the given content
$output->writeln(sprintf('After running command <info>%s</info>', $command->getName()));
// changes the exit code
$event->setExitCode(128);
}
Tip
This event is also dispatched when an exception is thrown by the command.
It is then dispatched just after the ConsoleEvents::ERROR event.
The exit code received in this case is the exception code.
Additionally, the event is dispatched when the command is being exited on a signal. You can learn more about signals in the the dedicated section.
The ConsoleEvents::SIGNAL Event
Typical Purposes: To perform some actions after the command execution was interrupted.
When a command is interrupted, Symfony dispatches the ConsoleEvents::SIGNAL
event. Listen to this event so you can perform some actions (e.g. logging some
results, cleaning some temporary files, etc.) before finishing the command execution.
Listeners receive a ConsoleSignalEvent event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Component\Console\Event\ConsoleSignalEvent;
// ...
public function __invoke(ConsoleSignalEvent $event): void
{
// gets the signal number
$signal = $event->getHandlingSignal();
// sets the exit code
$event->setExitCode(0);
if (\SIGINT === $signal) {
echo "bye bye!";
}
}
It is also possible to abort the exit if you want the command to continue its execution even after the event has been dispatched, thanks to the abortExit() method:
1 2 3 4 5 6 7
use Symfony\Component\Console\Event\ConsoleSignalEvent;
// ...
public function __invoke(ConsoleSignalEvent $event): void
{
$event->abortExit();
}
Handling Signals
Signals are asynchronous notifications sent to a process in order to notify
it of an event that occurred. For example, when you press Ctrl + C in a
command, the operating system sends the SIGINT signal to it.
Tip
All the available signals (SIGINT, SIGQUIT, etc.) are defined as
constants of the PCNTL PHP extension. The extension has to be installed
for these constants to be available.
In a Symfony application, a command can handle these signals by implementing the SignalableCommandInterface and subscribing to one or more signals:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// src/Command/MyCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
#[AsCommand(name: 'app:my-command')]
class MyCommand implements SignalableCommandInterface
{
// ...
public function getSubscribedSignals(): array
{
// return here any of the constants defined by PCNTL extension
return [\SIGINT, \SIGTERM];
}
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
{
if (\SIGINT === $signal) {
// ...
}
// ...
// return an integer to set the exit code, or
// false to continue normal execution
return 0;
}
}
This registers a signal handler for that command only. To run the same handler for all commands, use events instead.
You can also use setAlarmInterval()
to trigger a recurring alarm interval, causing the OS to deliver a SIGALRM signal
at the given interval (in seconds). Use these signals to perform periodic tasks during
long-running commands:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// src/Command/LongRunningCommand.php
namespace App\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\SignalableCommandInterface;
#[AsCommand(name: 'app:long-running')]
class LongRunningCommand implements SignalableCommandInterface
{
public function __invoke(Application $application): int
{
// trigger an alarm every 10 seconds
$application->setAlarmInterval(10);
// long-running processing...
return Command::SUCCESS;
}
public function getSubscribedSignals(): array
{
return [\SIGALRM];
}
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
{
// e.g. ping the database to keep the connection alive
return false;
}
}
Each time the SIGALRM signal fires, the application also dispatches a
ConsoleAlarmEvent. Listen to it to
run the periodic logic outside the command (e.g. in a listener shared by several
long-running commands):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/EventListener/ConsoleAlarmListener.php
namespace App\EventListener;
use Symfony\Component\Console\Event\ConsoleAlarmEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
class ConsoleAlarmListener
{
public function __invoke(ConsoleAlarmEvent $event): void
{
// e.g. ping the database to keep the connection alive
// keep the command running after handling the alarm
$event->abortExit();
}
}
Note
Unlike the other console events, this event is
dispatched using its class name, so the event argument of
#[AsEventListener] is not needed. In standalone applications, register
the listener with $dispatcher->addListener(ConsoleAlarmEvent::class, $listener).
Note
The alarm feature requires the pcntl PHP extension and is not available
on Windows.
Symfony doesn't handle any signal received by the command (not even SIGKILL,
SIGTERM, etc). This behavior is intended, as it gives you the flexibility to
handle all signals e.g. to do some tasks before terminating the command.
Tip
If you need to fetch the signal name from its integer value (e.g. for logging), you can use the getSignalName() method.
Profiling Commands
Symfony allows you to profile the execution of any command, including yours. First,
make sure that the debug mode and the profiler
are enabled. Then, add the --profile option when running the command:
1
$ php bin/console --profile app:my-command
Symfony will now collect data about the command execution, which is helpful to debug errors or check other issues. When the command execution is over, the profile is accessible through the web page of the profiler.
Tip
If you run the command in verbose mode (adding the -v option), Symfony
will display in the output a clickable link to the command profile (if your
terminal supports links). If you run it in debug verbosity (-vvv) you'll
also see the time and memory consumed by the command.
Among the collected data, the performance panel displays the duration of each argument value resolver used during the command execution, which helps spot slow resolvers.
8.1
The tracing of console argument value resolvers in the profiler's performance panel was introduced in Symfony 8.1.
Warning
When profiling the messenger:consume command from the Messenger
component, add the --no-reset option to the command or you won't get any
profile. Moreover, consider using the --limit option to only process a few
messages to make the profile more readable in the profiler.
Building a Single-Command Application
When building a command line tool, you may not need to provide several commands. In that case, having to pass the command name each time is tedious. Use the SingleCommandApplication class to create an application that runs a single command, without having to pass its name:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication;
new SingleCommandApplication()
->setName('My Super Command') // optional
->setVersion('1.0.0') // optional
->setCode(function (OutputInterface $output, #[Argument] string $directory = '.', #[Option] string $format = 'txt'): int {
// output arguments and options
return Command::SUCCESS;
})
->run();
Changing the Default Command
The console application runs the list command when no command name is
passed. To change the default command, pass the name of your command to the
setDefaultCommand() method:
1 2 3 4 5 6 7 8
// application.php
use App\Command\HelloWorldCommand;
use Symfony\Component\Console\Application;
$application = new Application();
$application->addCommand(new HelloWorldCommand());
$application->setDefaultCommand('hello:world');
$application->run();
Now, running php application.php without arguments runs the hello:world
command.
Warning
This feature has a limitation: you cannot pass any argument or option to the default command because they are ignored.
Pass true as the second argument of setDefaultCommand() to turn the
application into a single-command application, like the ones created with
SingleCommandApplication. The default command is then always run, without
having to pass its name, and it accepts arguments and options:
1
$application->setDefaultCommand('hello:world', true);
Using a PSR Container
8.1
The $container parameter of the Application class was introduced
in Symfony 8.1.
The Application class accepts an optional third argument, a PSR-11
ContainerInterface. When provided, the application automatically wires
several services from the container:
event_dispatcher: sets the event dispatcher viasetDispatcher()console.argument_resolver: sets the argument resolver viasetArgumentResolver()console.command_loader: sets the command loader viasetCommandLoader()console.command.ids: eagerly loads commands registered in the containerservices_resetter: resets services afterrun()completes
When using Symfony's ContainerInterface,
the kernel.environment and kernel.debug parameters are also displayed
in the application's long version output.
This makes it possible to build console applications with dependency injection without requiring HttpKernel or FrameworkBundle. Passing no container preserves all existing behavior:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\ContainerBuilder;
require __DIR__.'/vendor/autoload.php';
$container = new ContainerBuilder();
// ... register your services, commands, etc.
$container->compile();
$application = new Application('my-cli', '1.0', $container);
$application->run();
Using the ConsoleBundle
8.1
The ConsoleBundle was introduced in Symfony 8.1.
Building the container by hand as shown above works, but the Console component
also provides a ConsoleBundle that brings service autodiscovery,
autoconfiguration and autowiring to console applications, still without
requiring HttpKernel or FrameworkBundle.
The bundle needs a kernel to build the container. Use the kernel provided by the DependencyInjection component (read Building HTTP-less Applications for more details):
1 2 3 4 5 6 7 8 9 10
// src/Kernel.php
namespace App;
use Symfony\Component\DependencyInjection\Kernel\AbstractKernel;
use Symfony\Component\DependencyInjection\Kernel\KernelTrait;
class Kernel extends AbstractKernel
{
use KernelTrait;
}
Then, enable the bundle:
1 2 3 4
// config/bundles.php
return [
Symfony\Component\Console\ConsoleBundle::class => ['all' => true],
];
Finally, create the console executable. This example uses the Runtime component, but you can also boot the kernel yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Component\Console\Application;
require __DIR__.'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
$kernel->boot();
return new Application('my-cli', '1.0', $kernel->getContainer());
};
The kernel follows the same conventions as full Symfony applications:
config/bundles.php to enable bundles, config/packages/ to configure
them and config/services.yaml (or config/services.php) to register your
own services and commands.
On top of that, ConsoleBundle provides the following:
- autoconfiguration for commands: any service extending
Command or using the
#[AsCommand]attribute is added to the application and loaded lazily through the command loader; - autoconfiguration for the argument value resolvers, together with all the built-in resolvers;
- a listener that logs command errors and non-zero exit codes, when the EventDispatcher component is installed;
- the
dotenv:debugcommand, when the Dotenv component is installed.
ConsoleBundle declares ServicesBundle as a
required bundle, so you don't need to enable
it yourself. That bundle provides the core services used by the container, such
as parameter_bag, event_dispatcher, filesystem and clock.
Legacy Syntax to Define Commands
Instead of using invokable commands, you can also define commands by extending the Command class. Both syntaxes are supported, but invokable commands are recommended:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Command/CreateUserCommand.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;
// the name of the command is what users type after "php bin/console"
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... put here the code to create the user
}
}
You can optionally define a description, help message and the input options and
arguments by overriding the configure() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Command/CreateUserCommand.php
// ...
class CreateUserCommand extends Command
{
// ...
protected function configure(): void
{
$this
// the command description shown when running "php bin/console list"
->setDescription('Creates a new user.')
// the command help shown when running the command with the "--help" option
->setHelp('This command allows you to create a user...')
// add an argument required to your command
->addArgument('username', InputArgument::REQUIRED, 'How the user should be named?')
;
}
}
Tip
Using the #[AsCommand] attribute to define a description instead of
the setDescription() method retrieves the command description without
instantiating its class, which makes the php bin/console list command
run much faster.
The configure() method is called automatically at the end of the command
constructor. If your command defines its own constructor, set the properties
first and then call to the parent constructor, to make those properties
available in the configure() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// src/Command/CreateUserCommand.php
// ...
class CreateUserCommand extends Command
{
// ...
public function __construct(bool $requirePassword = false)
{
// best practices recommend to call the parent constructor first and
// then set your own properties. That wouldn't work in this case
// because configure() needs the properties set in this constructor
$this->requirePassword = $requirePassword;
parent::__construct();
}
protected function configure(): void
{
$this
// ...
->addArgument('password', $this->requirePassword ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'User password')
;
}
}
Learn More
- How to Call Other Commands
- How to Call a Command from a Controller
- Cursor Helper
- Debug Formatter Helper
- Formatter Helper
- Process Helper
- Progress Bar
- Progress Indicator
- Question Helper
- Table Helper
- Tree Helper
- Console Input (Arguments & Options)
- How to Make Commands Lazily Loaded
- Prevent Running the Same Console Command Multiple Times
- How to Style a Console Command
- Console Argument Value Resolvers
- Verbosity Levels
Console Helpers
The Console component also contains a set of "helpers": small tools that ease common tasks such as asking questions to the user, displaying progress bars and tables, formatting the output or running external processes: