CLI Commands

Same name and namespace in other branches
  1. main core/core.api.php \console_commands

Providing CLI commands for the Drupal CLI.

Drupal includes a CLI tool (invoked as `vendor/bin/dr`) built on the Symfony Console component. Modules can provide their own commands that are automatically discovered.

Creating a command

To provide a command, place a class in your module's `src/Command/` directory and apply the `#[AsCommand]` attribute to declare the command name and description. Command names follow the convention `module:action` (e.g. `my_module:hello`).

There are two types of commands:

Extending Symfony's Command class

One style extends `\Symfony\Component\Console\Command\Command` and implements `execute()`:

use Drupal\Component\Datetime\TimeInterface;
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;
class HelloCommand extends Command {
  public function __construct(private readonly TimeInterface $time) {
    parent::__construct();
  }
  protected function execute(InputInterface $input, OutputInterface $output) : int {
    $io = new SymfonyStyle($input, $output);
    $now = new \DateTimeImmutable('@' . $this->time
      ->getRequestTime());
    $io->success('Hello! The time is ' . $now->format('r'));
    return Command::SUCCESS;
  }

}

Invokable command classes

As an alternative, a plain class can define a single `__invoke()` method instead of extending `\Symfony\Component\Console\Command\Command`. Symfony Console attributes such as `#[Argument]`, `#[Option]`, and `#[Ask]` can be applied to individual parameters to declare arguments, options, and interactive prompts, removing the need for a `configure()` method. Drupal services can be injected via the constructor as normal.

use Drupal\Component\Datetime\TimeInterface;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Ask;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand {
  public function __construct(protected readonly TimeInterface $time) {
  }
  public function __invoke(OutputInterface $output, #[Argument('The name to greet.')] #[Ask('Who should be greeted?', 'World')] string $name, #[Option('Shout the greeting.')] bool $shout = FALSE) : int {
    $now = new \DateTimeImmutable('@' . $this->time
      ->getRequestTime());
    $greeting = "[{$now}] Hello, {$name}!";
    $output->writeln($shout ? strtoupper($greeting) : $greeting);
    return Command::SUCCESS;
  }

}

Auto-discovery and dependency injection

`\Drupal\Core\DependencyInjection\Compiler\ConsoleCompilerPass` scans each installed module's `src/Command/` directory at container compile time. Any class carrying the `#[AsCommand]` attribute is automatically registered as an autowired service tagged `console.command` — no service YAML entry is needed. Constructor dependencies are resolved from the service container automatically.

After Drupal bootstraps, `\Drupal\Core\Command\DrupalApplication` registers all tagged commands.

Registering via service tag

Commands that do not use `#[AsCommand]` — for example, those that declare their name only via `configure()` — are not auto-discovered and must be registered explicitly as a service tagged `console.command` in a module's `*.services.yml` file:


my_module.hello_command:
  class: Drupal\my_module\Command\HelloCommand
  tags:
    - { name: console.command }

See also

\Drupal\Core\Command\DrupalApplication

\Drupal\Core\DependencyInjection\Compiler\ConsoleCompilerPass

Parent topics

File

core/core.api.php, line 2875

Classes

Title Sort descending File name Summary
DrupalApplication core/lib/Drupal/Core/Command/DrupalApplication.php Customize the Symfony Application for Drupal.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.