You too can become a hero and support this Blog at GitHub Sponsors or Patreon.

More about support

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

That is basically all you have to know about executing commands. However there is on very popular alternative (or addition) to the typo3 binary provided by the core. And that is the typo3-console (see at Packagist) by Helmut Hummel (Twitter). Let's have a quick look at this before we move on.

Top

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.

Top

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.

Top

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:

Top

Attribute - #[AsCommand]

Since TYPO3 12

Since 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.

Top

Services.yaml

Since TYPO3 10

With 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).

Top

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:

  1. configure() (optional)
  2. initialize() (optional)
  3. interact() (optional)
  4. execute() (required)

Top

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.

Top

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.

Top

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.

Top

Further Information