This post is an updated version of an older post that covered the implementation of CLI commands in TYPO3 6 and 7.
The old post can be found here.
CLI stands for Command Line Interface and in combination with TYPO3 it means most of the time that we connect to the server where our TYPO3 is running and execute tasks on the command line. This comes in handy during deployment where we want several tasks executed. Tasks such as clearing the cache, adding database fields or importing data. CLI commands are used for many more things. We will now have a look on how to execute existing commands and how to add our own.
Executing CLI Commands
TYPO3 comes with a dedicated entry point for CLI requests. If you download the core via wget --content-disposition get.typo3.org/9
you will find this binary under typo3/sysext/core/bin/typo3
. However, the TYPO3 core requires the package typo3/cms-cli
(see on Packagist) in its composer.json
. So, if you install TYPO3 via composer (as you should) you will have the binary available at whatever place you told composer to provide binaries. This is vendor/bin
by default an can be changed in the root composer.json
of you project:
"config": {
"bin-dir": "bin"
}
The binary is nothing more than an instantiation of the CommandApplication. It looks like this:
#!/usr/bin/env php
<?php
/**
* Command Line Interface module dispatcher
* that executes commands
*/
call_user_func(function() {
$classLoader = require __DIR__ . '/../../autoload.php';
\TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::run(1, \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI);
\TYPO3\CMS\Core\Core\Bootstrap::init($classLoader)->get(\TYPO3\CMS\Core\Console\CommandApplication::class)->run();
});
After locating the binary you can call it from the command line to get a list of all available commands. For this blog it looks like this:
./vendor/bin/typo3
TYPO3 CMS 9.5.4
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
help Displays help for a command
list Lists commands
backend
backend:lock Lock the TYPO3 Backend
backend:unlock Unlock the TYPO3 Backend
cleanup
cleanup:deletedrecords Permanently deletes all records marked as "deleted" in the database.
cleanup:flexforms Updates all database records which have a FlexForm field and the XML data does not match the chosen datastructure.
cleanup:lostfiles Looking for files in the uploads/ folder which does not have a reference in TYPO3 managed records.
cleanup:missingfiles Find all file references from records pointing to a missing (non-existing) file.
cleanup:missingrelations Find all record references pointing to a non-existing record
cleanup:multiplereferencedfiles Looking for files from TYPO3 managed records which are referenced more than once
cleanup:orphanrecords Find and delete records that have lost their connection with the page tree.
cleanup:rteimages Looking up all occurrences of RTEmagic images in the database and check existence of parent and copy files on the file system plus report possibly lost RTE files.
extbase
extbase:help:help [extbase:help] The help command displays help for a given command: ./typo3/sysext/core/bin/typo3 extbase:help <command identifier>
extension
extension:activate [extensionmanager:extension:install|extension:install] Activates an extension by key
extension:deactivate [extensionmanager:extension:uninstall|extension:uninstall] Deactivates an extension by extension key
extension:list Shows the list of extensions available to the system.
language
language:update [lang:language:update] Update the language files of all activated extensions
referenceindex
referenceindex:update Update the reference index of TYPO3
scheduler
scheduler:run Start the TYPO3 Scheduler from the command line.
site
site:list Shows the list of sites available to the system.
site:show Shows the configuration of the specified site. Specify the identifier via "site:show <identifier>".
swiftmailer
swiftmailer:spool:send Sends emails from the spool
syslog
syslog:list Show entries from the sys_log database table of the last 24 hours.
upgrade
upgrade:list List available upgrade wizards.
upgrade:run Run upgrade wizard. Without arguments all available wizards will be run.
Note that the list of commands depends on what extensions you have installed. Single commands are grouped by the (system) extension they are provided by and have an identifier with multiple parts separated by colons. Your custom commands will be listed here as well but more on that later.
To call a command you just have to specify its full identifier after calling the binary. As an example let's run the scheduler (the identifier is scheduler:run
):
./vendor/bin/typo3 scheduler:run
Some commands need arguments which are simply passed after the identifier. For example:
vendor/bin/typo3 backend:lock https://usetypo3.com/404
An Alternative: typo3-console
To make use of the typo3-console you can simply require it in your composer.json
.
composer require helhum/typo3-console
The binary of typo3-console will be available as typo3cms
and located in the composer binary directory as well. To quote the Readme.md from the github-repository:
[typo3-console] ships many commands to execute TYPO3 actions, which otherwise would only be accessible via the TYPO3 backend. This makes TYPO3 Console a perfect companion for development, deployment, Docker setups, continuous integration workflows or anything else where automation is required or beneficial.
The additional commands provided by the console can be seen when the binary is called. Let's have a look:
./vendor/bin/typo3cms
TYPO3 Console 5.6.0
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
help Displays help for a command
list Lists commands
backend
backend:createadmin Create admin backend user
backend:lock Lock backend
backend:lockforeditors Lock backend for editors
backend:unlock Unlock backend
backend:unlockforeditors Unlock backend for editors
cache
cache:flush Flush all caches
cache:flushgroups Flush all caches in specified groups
cache:flushtags Flush cache by tags
cache:listgroups List cache groups
cleanup
cleanup:deletedrecords Permanently deletes all records marked as "deleted" in the database.
cleanup:flexforms Updates all database records which have a FlexForm field and the XML data does not match the chosen datastructure.
cleanup:lostfiles Looking for files in the uploads/ folder which does not have a reference in TYPO3 managed records.
cleanup:missingfiles Find all file references from records pointing to a missing (non-existing) file.
cleanup:missingrelations Find all record references pointing to a non-existing record
cleanup:multiplereferencedfiles Looking for files from TYPO3 managed records which are referenced more than once
cleanup:orphanrecords Find and delete records that have lost their connection with the page tree.
cleanup:rteimages Looking up all occurrences of RTEmagic images in the database and check existence of parent and copy files on the file system plus report possibly lost RTE files.
cleanup:updatereferenceindex Update reference index
configuration
configuration:remove Remove configuration option
configuration:set Set configuration value
configuration:show Show configuration value
configuration:showactive Show active configuration value
configuration:showlocal Show local configuration value
database
database:export Export database to stdout
database:import Import mysql queries from stdin
database:updateschema Update database schema (TYPO3 Database Compare)
documentation
documentation:generatexsd Generate Fluid ViewHelper XSD Schema
extension
extension:list List extensions that are available in the system
extension:setup Set up extension(s)
extension:setupactive Set up all active extensions
frontend
frontend:request Submit frontend request
install
install:extensionsetupifpossible Setup TYPO3 with extensions if possible
install:fixfolderstructure Fix folder structure
install:generatepackagestates Generate PackageStates.php file
install:setup TYPO3 Setup
language
language:update Update the language files of all activated extensions
scheduler
scheduler:run Run scheduler
site
site:list Shows the list of sites available to the system.
site:show Shows the configuration of the specified site. Specify the identifier via "site:show <identifier>".
swiftmailer
swiftmailer:spool:send Sends emails from the spool
syslog
syslog:list Show entries from the sys_log database table of the last 24 hours.
upgrade
upgrade:all Execute all upgrade wizards that are scheduled for execution
upgrade:checkextensionconstraints Check TYPO3 version constraints of extensions
upgrade:list List upgrade wizards
upgrade:wizard Execute a single upgrade wizard
Please note that this list includes all tasks already seen above and will contain custom commands as well. Of course this exact list is only a snapshot and can change in the future as commands are likely to be added, removed or renamed in later versions.
However, this list contains very useful tasks that are not part of the out-of-the-box commands of the TYPO3 core. For example the console provides cache management, configuration access, PackageStates.php generation and much more. I won't go into detail here. If you are interested, read Helmuts blog posts tagged with #console or join the #typo3-console slack channel over at the TYPO3 slack. I'll say as much as that the console is a great tool that is used in many projects in all TYPO3 deployment processes I know about.
After this quick excursion let's head back to the core.
We will quickly recap the development of how CLI-Commands were implemented in the history of TYPO3. Finally we will learn how to implement our own custom CLI tasks according to the current best practices.
A short history of TYPO3 and CLI
The implementation of CLI commands in TYPO3 has come a long way. In the old days the entry point for CLI requests was typo3/cli_dispatch.phpsh
. This entry point was deprecated in TYPO3 8.7 (Patch, Deprecation RST) and finally removed in TYPO3 9 (Breaking RST).
The old way to implement Commands was to write so called CommandLineController
and register them in $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']
. This has been deprecated in TYPO3 8.6 (Patch, Deprecation RST). The next thing was to use extbase for CommandControllers. Since TYPO3 9.4 those are deprecated (Patch, Deprecation RST) as well! Let's quote from the commit message of that patch to understand the motivation behind this:
The concept of Extbase Command Controllers has been superseded
with symfony/console and its integration into TYPO3 CLI in TYPO3 v8.0,
and contains all features necessary to build commands of any kind
of complexity.
As stated in this commit message the next iteration in the CLI integration in TYPO3 is symfony console, which has been fully integrated in TYPO3 core with TYPO3 8.0 (Patch, Feature RST). The console component is a popular and powerful so-to-say mini framework for PHP CLI applications. As a last part of this post we have a look how to add our own commands with symfony console to TYPO3.
Adding a Symfony Console Command to TYPO3
Through the years, there have been several ways to register CLI commands. Pick the most recent one, according to the TYPO3 version you use:
Attribute - #[AsCommand]
Since TYPO3 12Since TYPO3 v12, CLI commands can be registered simply by adding the PHP attribute #[AsCommand] that comes with the symfony component;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'custom:doIt', description: 'A description', aliases: ['custom:doIt-alias'])]
class CustomCommand extends Command
{
}
However, using the symfony attribute in a TYPO3 context is a bit of an odd choice, because TYPO3 CLI commands can be configured to be schedulable through the TYPO3 scheduler module. Of course the symfony attribute knows nothing about the scheduable: true|false
option.
All CLI commands registered with the #[AsCommand]
attribute, will be schedulable: true
and cannot be set to schedulable: false
. This means if we want our CLI command to not show up in the scheduler, we still need to register it in the Services.yaml
.
Services.yaml
Since TYPO3 10With TYPO3 10 LTS the Commands.php
registration is deprecated (RST) and commands should be registered via the Services.yaml:
services:
Vendor\MyProductExtension\Command\ImportCommand:
tags:
- name: 'console.command'
command: 'products:import'
schedulable: false
This registration is still valid in TYPO3 v13 (see above).
Configuration/Commands.php (outdated)
In TYPO3 9LTS and 8LTS, commands are registered in the file Configuration/Commands.php
. These files are collected from every extension that provides one. The file is supposed to return an array with the commands provided by the extension. Let's have a look at an example:
return [
'products:import' => [
'class' => \Vendor\MyProductExtension\Command\ImportCommand::class,
],
];
This will register the command products:import
and it tells TYPO3 which class contains the commands code. That is it.
For our convenience all symfony commands are also available within a scheduler task since TYPO3 9LTS (Patch, Feature RST). A functionality already known from the former extbase commands. And as it was possible with extbase commands it is also possible with symfony commands to exclude them from the scheduler task. This can be achieved by setting 'schedulable' => false
in the Commands.php (Patch, Feature RST).
Let's add this to the example to make it clear:
return [
'products:import' => [
'class' => \Vendor\MyProductExtension\Command\ImportCommand::class,
'schedulable' => false,
],
];
Note that this is not possible in TYPO3 8 LTS as there is also no scheduler task for symfony commands.
Implementing a Command
After we registered a new command, we have to implement it. The actual implementation will be a mere symfony command, which calls four functions in a specific order:
configure()
(optional)initialize()
(optional)interact()
(optional)execute()
(required)
configure()
After the constructor the (optional) method configure()
is called. This method can be used to specify arguments, options as well as help text. Further information can be found in the symfony documentation. Let's look at an example:
protected function configure()
{
$this
->setDescription('Imports product records.')
->setHelp('This command imports product records into the database...')
->addArgument('file', InputArgument::REQUIRED, 'path to file to import from')
->addOption('dryRun', 'd', InputOption::VALUE_OPTIONAL, 'Perform a dry run?', false)
;
}
Let's have a look at the four things we can add to our command in the configure()
method:
- Description: The short description shown while running "bin/typo3 list"
- Help: The full command description shown when running the command with
--help
- Arguments: Arguments are the strings - separated by spaces - that come after the command name itself. They are ordered, and can be optional or required.
- Options: Unlike arguments, options are not ordered (meaning you can specify them in any order) and are specified with two dashes (e.g.
--dryRun
). Options are always optional, and can be setup to accept a value (e.g.--dir=src
) or simply as a boolean flag without a value (e.g.--dryRun
).
For a more detailed description and examples on how to use Arguments and Options in the command, please refer to Console Input (Arguments & Options) in the official smyfony console documentation.
initialize() and interact()
The initialize()
method is next in the chain of executed functions. initialize()
is executed before the interact()
and the execute()
methods. Its main purpose is to initialize variables used in the rest of the command methods. The implementation in the Command.php class (see at GitHub) looks like this:
/**
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
Next is interact()
.
interact()
is executed after initialize()
and before execute()
. Its purpose is to check if some of the options/arguments are missing and interactively ask the user for those values. This is the last place where you can ask for missing options/arguments. After this method, missing required options/arguments will result in an error. interact()
will not be called if --no-interaction
is passed to the command.
The interactivity is an interesting aspect of the symfony commands. Let's look at an example as well:
protected function interact(InputInterface $input, OutputInterface $output)
{
if (!$input->getArgument('file')) {
$output->writeln('Please provide a path to the file to import from');
$helper = $this->getHelper('question');
$question = new Question('Path: ');
$file = $helper->ask($input, $output, $question);
$input->setArgument('file', $file);
}
}
After ineract()
the input is passed to the execute()
method where the main logic of the command is supposed to be.
execute()
execute()
is the only method that a symfony command needs to implement, the before mentioned methods are all optional. The method receives two objects. One for the $input
to get arguments and options from and one for the $output
to generate any kind of output. There is a whole chapter in the symfony documentation covering styling the output of a symfony comand: How to Style a Console Command. The output options include (but are not limited to) tables, progress bars, coloring, headlines, listings and sections.
I will not go into detail here on how to use each of those output options. Instead, I'll add an example of an execute()
method that contains some output as well:
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Importing Products');
if ($input->getOption('dryRun')) {
$this->dryRun = true;
$io->note('THIS IS A DRY RUN.');
}
$file = $input->getArgument('file');
// proceed with import
return 0;
}
We can now execute our new command with
./vendor/bin/typo3 products:import --file=/path/to/file --dryRun
With the symfony console component TYPO3 has implemented a very popular and well maintained solution. This feels very much in line with the general approach to increase the use of standard solutions from the PHP world for common tasks of which providing an interface for CLI commands is one example. Include solid solutions from outside the TYPO3 world allows TYPO3 to focus on its core feature set: being a flexible and powerful CMS.
Many thanks to all contributers who made these improvements possible. Many thanks also to all the readers of this post. As always please don't hesitate to contact me if important information is missing or if anything seems wrong or unclear. I am always happy to improve postings over time to make them solid sources for TYPO3 developers. Also thanks to Marcus Schwemer who's Blog post at typo3worx.eu (see below) on this topic reminded me to update my outdated information on TYPO3 and CLI.
Further Information
-
Symfony Console in TYPO3 – An Introduction, Blog Post by Marcus Schwemer, 2019
-
TYPO3 CMS shell scripts (CLI mode), official TYPO3 Documentation
-
Console Commands, official Symfony Documentation