You are currently viewing Symfony Console in TYPO3 – An Introduction

Symfony Console in TYPO3 – An Introduction

Command controller are a nice way to execute things in TYPO3 context on the command line. Since TYPO3 8 it is possible to use the Symfony Console to create and execute those commands natively. In this article I describe, how to use them and which possibilities they offer.

Console commands are triggered via the command line and executing tasks with in the TYPO3 context. Probably all of us know the “TYPO3 Console” created and maintained by Helmut Hummel. But there is also a binary, which ships with the TYPO3 core. It is available at ./vendor/bin/typo3, formerly known as cli_dispatch.sh. The path to the new binary may vary slightly depending where the bin directory resides.

Until back then it was possible to extend the functionality using TYPO3s “Extbase Command Controllers”. The support of native Symfony Console Command is shipping TYPO3 version 8.0, so quite a long time. I did not see many symfony console commands “in the wild”, so I decided to write this post about it.

First steps to a command skeleton

Register Symfony console commands in TYPO3

The first step is to register the new command within TYPO3. This happens in your extension in the file Configuration/Commands.php. It takes and simple array with the names of the tasks and a corresponding class name.

<?php 

return [
    'mycoolextension:import_data' => [
        'class' => \Vendor\MyCoolExtension\Command\ImportDataCommand::class
    ],
    'mycoolextension:export_data' => [
        'class' => \Vendor\MyCoolExtension\Command\ExportDataCommand::class
    ]
];

Create a Symfony console command

In the next step the registered class name must be created. A very basic command skeleton looks like this:

<?php

namespace \Vendor\MyCoolExtension\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

class ImportDataCommand extends Command 
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Do nothing
    }

}

Only the function execute is needed. Otherwise the execution will fail with an exception. In this case here, it will just do nothing. ;-) The execute function is the last part of the lifecycle of a symfony command.

The Symfony console command lifecycle

In a complete lifecycle of a command there are four steps: Configure, initialize, interact and execute.

Configure

The configuration of a command is the first step to run it successfully. The configuration provides a description, the options and the arguments and some more settings.

An argument can be mandatory or optional and is just a plain string after the command. The last argument can also be handled as an array. The name of an option is prefixed with a double dash, takes a single value and is always optional.

Here is an example of the configuration. This is just the plain configure function that needs to be inserted into the command class from above.

    protected function configure()
    {
        $this->setDescription(
			'A string that describes the command, when while listing'
		);
		$this->setHelp(
			'This string is displayed, when calling the *bin/typo3 help mycoolextension:import_data'
		);
        $this->addArgument(
            'First Name', 
            InputArgument::REQUIRED, 
            'My description for the argument First Name',
			'The default value (if argument is optional)
		);
		$this->addOption(
            'nothing',
            null,
            InputOption::VALUE_NONE,
            ' Do nothing'
        );

    }

addArgument takes up to four arguments:

  • the argument name: ‘First Name’ . It is used to access the argument value in the following steps of the lifecycle
  • the mode, whether it is optional or not
  • a short description and
  • a default value (if the argument is optional)

There are some more configuration options, like defining aliases for the command or adding usage examples.

addOption has five arguments

  • the name
  • the shortcut (can be null)
  • the mode: none, required, optional or array
  • a description and
  • a default value

Initialize

This step is optional. It is there to transform the arguments and options provided by the user and add data to custom variables of this command. The generated data can be used in the following steps.

Interact

Also the interaction step is optional, but it is the last step before the user input regarding the requirements is evaluated. The common use case is to ask the user one or more questions, about his input. An example is, that the command requires one argument, but none is given. Now it is possible to ask the user a question to provide the missing data.

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        if (!$input->getArgument('First Name')) {
            $output->writeln('We need your first name');
            
			$helper = $this->getHelper('question');
            $question = new Question('Please give me your first name: ');
            $firstName = $helper->ask($input, $output, $question);

            $input->setArgument('First Name', $firstName);

        }
    }

The function interact takes two arguments: the input and the output interface. They take care of the communication with the user. In the code example above makes use of the question helper, that asks the to provide additional information, returns the input and is used to set the value of the missing argument.

At this point the modes set in the configuration are evaluated. If one value does not meet the requirements, the command fails here. If all is OK, the command will move on to the last step of the lifecycle.

Execute

In this step, the command does its business logic and is executed. This does not mean, that no interaction with the user is not possible any more. With the question helper from above it is still possible to gather further information from the user.

    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $table = 'be_users';

        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable($table);

        $data = $queryBuilder
            ->select('*')
            ->from($table)
            ->where($queryBuilder->expr()->eq('deleted', 0))
            ->setFirstResult(0)
            ->setMaxResults(5)
            ->execute()
            ->fetchAll();

        $table = new Table($output);
        $table->setHeaders(['uid', 'username', 'email']);
        $table->setRows([
            [$data[0]['uid'], $data[0]['username'], $data[0]['email']],
            [$data[1]['uid'], $data[1]['username'], $data[1]['email']],
            [$data[2]['uid'], $data[2]['username'], $data[2]['email']],
            [$data[3]['uid'], $data[3]['username'], $data[3]['email']]
        ]);
        $table->render();

        $output->writeln('Thanks ' . $input->getArgument('First Name'));
        $output->writeln('Bye');
        $output->writeln(' ');
    }

Within TYPO3 you have the full power of the CMS. In the above example I used the query builder to return five backend users and list them in a table on the console and used the input argument for a final salutation. Ok, that’s not that sophisticated, but shows the full access to TYPO3 at this point.

Input, output and helpers

Input

All input possibilities are included in the above examples: the arguments, the options and the question helper.

Output

The easiest case is to output just single lines using the writeln function. Whatever you pass, it is printed to the cli. There are also several ways to style the output and make it more “eye candy” for the user. For more info see:

Another feature for the output is the use of sections. Sections are there to group output and can be addressed independently. It is also possible to clear and overwrite output, which had already hit the cli.

Here is a short example from the symfony documentation:

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $section1 = $output->section();
        $section2 = $output->section();

        $section1->writeln('Hello');
        $section2->writeln('World!');
        // Output displays "Hello\nWorld!\n"

        // overwrite() replaces all the existing section contents with the given content
        $section1->overwrite('Goodbye');
        // Output now displays "Goodbye\nWorld!\n"

        // clear() deletes all the section contents...
        $section2->clear();
        // Output now displays "Goodbye\n"

        // ...but you can also delete a given number of lines
        // (this example deletes the last two lines of the section)
        $section1->clear(2);
        // Output is now completely empty!
    }

Beyond these “basic” input and output methods, there are some other so called helpers.

Helpers

Helpers are are classes that make it easier to fulfill certain tasks and take coding efforts from you.

I already showed two of them on the above examples: the question helper and the table. Other available helper are a

  • formatter helper
  • progress bar
  • process helper and a
  • debug formatter helper

These helpers can used anywhere in the code of the command. For the correct invocation and usage you can have a look at https://symfony.com/doc/current/components/console/helpers/index.html. There are all helpers described in short.

Conclusion

I came across using the Symfony console commands within TYPO3 just a couple of weeks ago, while researching for another topic (which needs still to be written ;-) ). On the other hand I wrote an extbase command controller short before … and I like this approach much more TBH: The interaction with the user and the output options, while executing the command.

Credits

I want to thank my supporters via patreon.com, who make this blog post possible. This time I welcome back Tomas Norre Mikkelsen as one of my patrons. Thanks for sponsoring me again!

If you appreciate my blog and want to support me, you can say “Thank You!”. Find out the possiblities here:

I found the blog post image on Unsplash . It was published by Javadh under the Unsplash License. It was modified by myself using pablo on buffer.