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

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!