Tutorial: Create a console command from scratch

A console command is always inside an extension. If you want to create one, kickstart a custom extension or use your site package extension.

Creating a basic 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 PHP attribute AsCommand for more details.

In this section we will create an empty command skeleton with no parameters or user interaction.

Create a class called DoSomethingCommand which extends \Symfony\Component\Console\Command\Command.

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

declare(strict_types=1);

namespace MyVendor\MyExtension\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;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'myextension:dosomething',
    description: 'A command that does nothing and always succeeds.',
    aliases: ['examples:dosomethingalias'],
)]
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
    {
        $io = new SymfonyStyle($input, $output);
        $io->info('Command needs to be implemented. ');
        return Command::SUCCESS;
    }
}
Copied!

The following two methods should be overridden by your class:

configure()
As the name suggests, this is where the command can be configured. Add a help text and/or define arguments and options.
execute()
Contains the command logic. Must return an integer. It is considered best practice to return the constants Command::SUCCESS or Command::FAILURE.

The above example can be run via the command line. If a newly created or edited command is not found, clear the cache first:

vendor/bin/typo3 cache:flush
vendor/bin/typo3 examples:dosomething
Copied!
typo3/sysext/core/bin/typo3 cache:flush
typo3/sysext/core/bin/typo3 examples:dosomething
Copied!

The command will return without a message as it does nothing but state that it has succeeded.

Example console command implementations

A command with parameters and arguments

packages/my_extension/Classes/Command/SendFluidMailCommand.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use T3docs\Examples\Exception\InvalidWizardException;

#[AsCommand(
    name: 'myextension:createwizard',
)]
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',
            );
    }
    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;
    }

    private function doMagic(SymfonyStyle $io, mixed $wizardName, bool $bruteForce): void
    {
        // do your magic here
    }
}
Copied!

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

vendor/bin/typo3 examples:createwizard [-b] [wizardName]
Copied!
typo3/sysext/core/bin/typo3 examples:createwizard [-b] [wizardName]
Copied!

Sending a FluidMail via command

packages/my_extension/Classes/Command/SendFluidMailCommand.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;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Mail\FluidEmail;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Site\SiteFinder;

#[AsCommand(
    name: 'myextension:sendmail',
)]
class SendFluidMailCommand extends Command
{
    public function __construct(
        private readonly SiteFinder $siteFinder,
        private readonly MailerInterface $mailer,
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        Bootstrap::initializeBackendAuthentication();

        // The site has to have a fully qualified domain name
        $site = $this->siteFinder->getSiteByPageId(1);
        $request = (new ServerRequest())
            ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE)
            ->withAttribute('site', $site);
        $GLOBALS['TYPO3_REQUEST'] = $request;
        // Send some mails with FluidEmail
        $email = new FluidEmail();
        $email->setRequest($request);
        // Set receiver etc
        $this->mailer->send($email);
        return Command::SUCCESS;
    }
}
Copied!