Writing custom commands
TYPO3 uses the Symfony Console component to define and execute command-line interface (CLI) commands. Custom commands allow extension developers to provide their own functionality for use on the command line or in the TYPO3 scheduler.
See also
- For a step-by-step guide, see: Tutorial: Create a console command.
Table of contents
The custom command class
To implement a console command in TYPO3 extend the
\Symfony\
class.
See also
Console commands in TYPO3 are based on the same technology as commands in Symfony. Find more information about Commands in the Symfony documentation.
Console command registration
There are two ways that a console command can be registered: you can use the
PHP Attribute AsCommand or register the command in your Services.
:
PHP attribute AsCommand
CLI commands can be registered by setting the attribute
\Symfony\
in the command class.
When using this attribute there is no need to register the command in
Services.
.
#[AsCommand(
name: 'examples:dosomething',
description: 'A command that does nothing and always succeeds.',
aliases: ['examples:dosomethingalias'],
)]
The following parameters are available:
name
- The name under which the command is available.
description
- Gives a short description. It will be displayed in the list of commands and the help information for the command.
hidden
- Hide the command from the command list by setting
hidden
totrue
. alias
- A command can be made available under a different name. Set to
true
if your command name is an alias.
If you want to set a command as non-schedulable it has to be registered via tag not attribute.
Tag console.command
in the Services.yaml
You can register the command in Configuration/Services.yaml
by adding the service
definition of your class as a tag
console.
:
services:
# ...
MyVendor\MyExtension\Command\DoSomethingCommand:
tags:
- name: console.command
command: 'examples:dosomething'
description: 'A command that does nothing and always succeeds.'
# Also an alias for the command can be configured
- name: console.command
command: 'examples:dosomethingalias'
alias: 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 will be
an empty space in the list of commands.
Making a command non-schedulable
A command can be set as disabled for the scheduler by setting
schedulable
to
false
. This can only be done when registering the command via
tag and not via attribute:
services:
# ...
MyVendor\MyExtension\Command\DoSomethingCommand:
tags:
- name: console.command
command: 'examples:dosomething'
description: 'A command that does nothing and cannot be scheduled.'
schedulable: false
Context of a command: No request, no site, no user
Commands are called from the console / command line and not through a web request. Therefore, when the code of your custom command is run by default there is no ServerRequest available, no backend or frontend user logged in and a request is called without context of a site or page.
For that reason Site Settings, TypoScript and TSconfig are not loaded by default, Extbase repositories cannot be used without taking precautions and there are many more limitations.
Extbase limitations in CLI context
Attention
It is not recommended to use Extbase repositories in a CLI context.
Extbase relies on frontend TypoScript, and features such as request-based TypoScript conditions may not behave as expected.
Instead, use the Query Builder or DataHandler when implementing custom commands.
Using the DataHandler in CLI commands
When using the DataHandler in a CLI command, backend user authentication is required. For more information see: Using the DataHandler in a Symfony command.
Initialize backend user
A backend user can be initialized inside the
execute
method as follows:
<?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\Bootstrap;
#[AsCommand(
name: 'myextension:dosomething',
)]
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 the DataHandler or other backend permission-handling-related tasks.
Simulating a frontend request in TYPO3 Commands
Executing a TYPO3 command in the CLI does not trigger a frontend (web)
request. This means that several request attributes required for link generation
via Fluid or TypoScript are missing by default. While setting the site
attribute in the request is a first step, it does not fully replicate the
frontend behavior.
See also
See chapter Simulating a frontend request.
A minimal request configuration may be sufficient for generating simple links or using FluidEmail:
<?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;
}
}
Note
Simulating a frontend request in CLI is possible but requires all bootstrapping steps to be manually carried out. While basic functionality, such as link generation, can work with a minimal setup, complex TypoScript-based link modifications, access restrictions, and context-aware rendering require additional configuration and may still behave differently from a real web request.
Create a command with arguments and interaction
Passing arguments
Since a command extends
\Symfony\
,
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:
Both arguments and properties can be registered in a command implementation by
overriding the configure
method. You can call methods add
and
add
to register them.
This argument can be retrieved with
$input->get
, the options with
$input->get
, for example:
<?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
}
}
User interaction on the console
You can create a
Symfony
console user interface using the
$input
and
$output
parameters of the
execute
function:
<?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:crazycalculator',
)]
final class CrazyCalculatorCommand extends Command
{
protected function execute(
InputInterface $input,
OutputInterface $output,
): int {
$io = new SymfonyStyle($input, $output);
$io->title('Welcome to our awesome extension');
$io->text([
'We will ask some questions.',
'Please take your time to answer them.',
]);
do {
$number = (int)$io->ask(
'Please enter a number greater 0',
'42',
);
} while ($number <= 0);
$operation = (string)$io->choice(
'Chose the desired operation',
['squared', 'divided by 0'],
'squared',
);
switch ($operation) {
case 'squared':
$io->success(sprintf('%d squared is %d', $number, $number * $number));
return Command::SUCCESS;
default:
$io->error('Operation ' . $operation . 'is not supported. ');
return Command::FAILURE;
}
}
}
The
$io
variable can be used to generate output and prompt for
input.
Dependency injection in console commands
You can use dependency injection (DI) in console commands via constructor injection or method injection.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Command;
use Psr\Log\LoggerInterface;
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 T3docs\Examples\Http\MeowInformationRequester;
#[AsCommand(
name: 'myextension:dosomething',
)]
final class MeowInformationCommand extends Command
{
public function __construct(
private readonly MeowInformationRequester $requester,
private readonly LoggerInterface $logger,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$this->requester->isReady()) {
$this->logger->error('MeowInformationRequester was not ready! ');
return Command::SUCCESS;
}
// Do awesome stuff
return Command::SUCCESS;
}
}
More about Symfony console commands
- See implementation of existing command controllers in the Core:
typo3/
sysext/*/ Classes/ Command - Symfony Command Documentation
- Symfony Commands: Console Input (Arguments & Options)