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.
Table of contents
Creating a basic command
In this section we will create an empty command skeleton with no parameters or user interaction.
See also
Create a class called
Do which extends
\Symfony\.
<?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: ['myextension: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;
}
}
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::orSUCCESS 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 myextension:dosomething
typo3/sysext/core/bin/typo3 cache:flush
typo3/sysext/core/bin/typo3 myextension:dosomething
The command prints a note that it still has to be implemented, and reports success.
Example console command implementations
A command with parameters and arguments
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Command;
use MyVendor\MyExtension\Exception\InvalidWizardException;
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 TYPO3\CMS\Core\Attribute\AsNonSchedulableCommand;
#[AsCommand(
name: 'myextension:createwizard',
)]
#[AsNonSchedulableCommand]
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
}
}
It uses attribute
# so that it can only be used
from the console and not from the Administration > Scheduler module.
This command takes one argument
wizard (optional) and one option (optional),
which can be added on the command line:
vendor/bin/typo3 myextension:createwizard [-b] [wizardName]
typo3/sysext/core/bin/typo3 myextension:createwizard [-b] [wizardName]
Sending a FluidMail via command
<?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 TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
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,
private readonly TemplatedEmailFactory $templatedEmailFactory,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// The site has to have a fully qualified domain name
$site = $this->siteFinder->getSiteByPageId(1);
// Simulate a frontend request to the site, so that links and resources
// in the email point to the site
$request = (new ServerRequest($site->getBase()))
->withAttribute(
'applicationType',
SystemEnvironmentBuilder::REQUESTTYPE_FE,
)
->withAttribute('site', $site);
// FluidEmail needs normalizedParams on the request, see
// https://forge.typo3.org/issues/111023
$request = $request->withAttribute(
'normalizedParams',
NormalizedParams::createFromRequest($request),
);
// Commands use create(), which reads the global mail configuration
$email = $this->templatedEmailFactory->create($request);
// Set receiver etc
$this->mailer->send($email);
return Command::SUCCESS;
}
}
Simulating a frontend request in TYPO3 commands explains which links work in such an email.