Tutorial

Create a console command from scratch

A console command is always situated in an extension. If you want to create one, kickstart a custom extension or use your sitepackage extension.

Creating a basic command

The extension kickstarter "Make" offers a convenient console command that creates a new command in an extension of your choice: Create a new console command with "Make". You can use "Make" to create a console command even if your extension was created by different means.

This command can be found in the Examples extension.

1. Register the command

New in version 12.4.8: The Symfony PHP attribute \Symfony\Component\Console\Attribute\AsCommand is now accepted to register console commands. See the section Use the PHP attribute to register commands for more details.

Register the command in Configuration/Services.yaml by adding the service definition for your class as tag console.command:

EXT:examples/Configuration/Services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  T3docs\Examples\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

  T3docs\Examples\Command\DoSomethingCommand:
    tags:
      - name: console.command
        command: 'examples:dosomething'
        description: 'A command that does nothing and always succeeds.'

The following attributes are available:

command

The name under which the command is available.

description

Give a short description. It will be displayed in the list of commands and the help information of the command.

schedulable

By default, a command can be used in the scheduler, too. This can be disabled by setting schedulable to false.

hidden

A command can be hidden from the command list by setting hidden to true.

Note

Despite using autoconfigure: true the commands have to be explicitly defined in Configuration/Services.yaml. It is recommended to always supply a description, otherwise there is an empty space in the list of commands.

2. Create the command class

Create a class called DoSomethingCommand extending \Symfony\Component\Console\Command\Command.

EXT:examples/Classes/Command/DoSomethingCommand.php
<?php

declare(strict_types=1);

/*
 * This file is part of the TYPO3 CMS project. [...]
 */

namespace T3docs\Examples\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class DoSomethingCommand extends Command
{
    protected function configure(): void
    {
        $this->setHelp('This command does nothing. It always succeeds.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Do awesome stuff
        return Command::SUCCESS;
    }
}

The following two methods should be overridden by your class:

configure()

As the name would suggest, allows to configure the command. The method allows to add a help text and/or define arguments and options.

execute()

Contains the logic when executing the command. Must return an integer. It is considered best practice to return the constants Command::SUCCESS or Command::FAILURE.

See also

A detailed description and an example can be found in the Symfony Command Documentation.

3. Run the command

The above example can be run via command line:

vendor/bin/typo3 examples:dosomething

The command will return without a message as it does nothing but stating it succeeded.

Note

If a newly created or changed command is not found, clear the cache:

vendor/bin/typo3 cache:flush

Use the PHP attribute to register commands

New in version 12.4.8.

CLI commands can be registered by setting the attribute \Symfony\Component\Console\Attribute\AsCommand on the command class. When using this attribute there is no need to register the command in the Services.yaml file.

Note

Only the parameters command, description and hidden are available. In order to overwrite the parameter schedulable use the registration via Services.yaml. By default, schedulable is true.

The example above can also be registered this way:

EXT:my_extension/Classes/Command/MyCommand.php
<?php

declare(strict_types=1);

namespace T3docs\Examples\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: 'examples:dosomething',
    description: 'A command that does nothing and always succeeds.',
)]
class DoSomethingCommand extends Command
{
    protected function configure(): void
    {
        $this->setHelp('This command does nothing. It always succeeds.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Do awesome stuff
        return Command::SUCCESS;
    }
}

Create a command with arguments and interaction

Passing arguments

Since a command extends Symfony\Component\Console\Command\Command, it is possible to define arguments (ordered) and options (unordered) using the Symfony command API. This is explained in depth on the following Symfony Documentation page:

Add an optional argument and an optional option to your command:

Class T3docs\Examples\Command\CreateWizardCommand
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

final class CreateWizardCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setHelp('This command accepts arguments')
            ->addArgument(
                'wizardName',
                InputArgument::OPTIONAL,
                'The wizard\'s name'
            )
            ->addOption(
                'brute-force',
                'b',
                InputOption::VALUE_NONE,
                'Allow the "Wizard of Oz". You can use --brute-force or -b when running command'
            );
    }
}

This command takes one optional argument wizardName and one optional option, which can be passed on the command line:

vendor/bin/typo3 examples:createwizard [-b] [wizardName]

This argument can be retrieved with $input->getArgument(), the options with $input->getOption(), for example:

Class T3docs\Examples\Command\CreateWizardCommand
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use T3docs\Examples\Exception\InvalidWizardException;

final class CreateWizardCommand extends Command
{
    protected function execute(
        InputInterface $input,
        OutputInterface $output
    ): int {
        $io = new SymfonyStyle($input, $output);
        $wizardName = $input->getArgument('wizardName');
        $bruteForce = (bool)$input->getOption('brute-force');
        try {
            $this->doMagic($io, $wizardName, $bruteForce);
        } catch (InvalidWizardException) {
            return Command::FAILURE;
        }
        return Command::SUCCESS;
    }
}

User interaction on the console

You can create a SymfonyStyle console user interface from the $input and $output parameters to the execute() function:

EXT:examples/Classes/Command/CreateWizardCommand.php
use Symfony\Component\Console\Style\SymfonyStyle;

final class CreateWizardCommand extends Command
{
    protected function execute(
        InputInterface $input,
        OutputInterface $output
    ): int {
        $io = new SymfonyStyle($input, $output);
        // do some user interaction
        return Command::SUCCESS;
    }
}

The $io variable can then be used to generate output and prompt for input:

Class T3docs\Examples\Command\CreateWizardCommand
use Symfony\Component\Console\Style\SymfonyStyle;
use T3docs\Examples\Exception\InvalidWizardException;

final class CreateWizardCommand extends Command
{
    private function doMagic(
        SymfonyStyle $io,
        mixed $wizardName,
        bool $bruteForce
    ) {
        $io->comment('Trying to create wizard ' . $wizardName . '...');
        if ($wizardName === null) {
            $wizardName = (string)$io->ask(
                'Enter the wizard\'s name (e.g. "Gandalf the Grey")',
                'Lord Voldermort'
            );
        }
        if (!$bruteForce && $wizardName === 'Oz') {
            $io->error('The Wizard of Oz is not allowed. Use --brute-force to allow it.');
            throw new InvalidWizardException();
        }
        $io->success('The wizard ' . $wizardName . ' was created');
    }
}

Dependency injection in console commands

You can use dependency injection (DI) in console commands by constructor injection or method injection:

Class T3docs\Examples\Command\MeowInformationCommand
use Psr\Log\LoggerInterface;
use T3docs\Examples\Http\MeowInformationRequester;

final class MeowInformationCommand extends Command
{
    public function __construct(
        private readonly MeowInformationRequester $requester,
        private readonly LoggerInterface $logger,
    ) {
        parent::__construct();
    }
}

Initialize backend user

A backend user can be initialized with this call inside execute() method:

EXT:some_extension/Classes/Command/DoBackendRelatedThingsCommand.php
use TYPO3\CMS\Core\Core\Bootstrap;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class DoBackendRelatedThingsCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        Bootstrap::initializeBackendAuthentication();
        // Do backend related stuff

        return Command::SUCCESS;
    }
}

This is necessary when using DataHandler or other backend permission handling related tasks.

More information