Symfony Console Beyond the Basics – Helpers and Other Tools

Share this article

This article was peer reviewed by Wern Ancheta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


It’s undeniable how useful console commands can be when developing software. Not too long ago we re-introduced the Symfony Console component.

This component allows us to create structured and testable CLI commands. We created some simple commands and tested them; but when our commands become bigger and more complex, we need a different set of tools.

This is what we are going to look at today: advanced Symfony console tools.

Let’s create a command that we can use to show some of these features. Most of the basic functionality was shown in the re-introduction to the Symfony console article, so be sure to check it before advancing – it’s a quick but useful read!

Console screenshot

Installation

composer require symfony/console

Essential information about Composer can be found here, and if you’re not familiar with well designed isolated PHP environments in which to develop your PHP apps like Vagrant, we have a fantastic book explaining it all in depth available for purchase here.

Creating our command

Let’s create a command for an all time favorite: Fizzbuzz.

Fizzbuzz is a simple problem often used in programming interviews to assert the programming competence of the interviewee. The definition of Fizzbuzz normally comes in the following form:

Write a program that prints the numbers from 1 to x. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five, print “FizzBuzz”.

Our command will receive an argument which will be the top limit for Fizzbuzz.

First of all, let’s create our Fizzbuzz class.

<?php 
declare(strict_types=1);

namespace FizzBuzz;

class Fizzbuzz{

    public function isFizz(int $value): bool{
        if($value % 3 === 0){
            return true;    
        }
        return false;
    }

    public function isBuzz(int $value): bool{
        if($value % 5 === 0){
            return true;    
        }
        return false;
    }

    public function calculateFizzBuzz(int $number): bool{
        if($this->isFizz($number) && $this->isBuzz($number)){
            echo "FizzBuzz \n";
            return true;
        }
        if($this->isFizz($number)){
            echo "Fizz \n";
            return true;
        }
        if($this->isBuzz($number)){
            echo "Buzz \n";
            return true;
        }
        echo $number . "\n";
        return true;
    }

    public function firstNFizzbuzz(int $maxValue): void{
        $startValue = 1;

        while($startValue <= $maxValue){
            $this->calculateFizzBuzz($startValue);
            $startValue++;
        }
    }
}

Pretty straightforward. The firstNFizzbuzz() method prints the results of Fizzbuzz for a $maxValue of numbers. It does this by calling the calculateFizzBuzz() method recursively.

Next, let’s write our command. Create a FizzCommand.php file with the following contents:

<?php

namespace FizzBuzz;

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

use FizzBuzz\Fizzbuzz;

class FizzCommand extends Command{

    protected function configure(){
        $this->setName("FizzBuzz:FizzBuzz")
                ->setDescription("Runs Fizzbuzz")
                ->addArgument('Limit', InputArgument::REQUIRED, 'What is the limit you wish for Fizzbuzz?');
    }

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

        $fizzy = new FizzBuzz();
        $input = $input->getArgument('Limit');

        $result = $fizzy->firstNFizzbuzz($input);
    }

}

And finally our console file.

#!/usr/bin/env php

<?php 

require_once __DIR__ . '/vendor/autoload.php'; 

use Symfony\Component\Console\Application; 
use FizzBuzz\FizzCommand;

$app = new Application();
$app->add(new FizzCommand());
$app->run();

Here we create a new Console Application and register our FizzCommand() into it. Don’t forget to make this file executable.

We can now check if our command is correctly registered by running the ./console command. We can also execute our command with ./console FizzBuzz:Fizzbuzz 25. This will calculate and print the Fizzbuzz results from 1 to 25.

Up until now, we haven’t done anything new. But there are a couple of ways we can improve our command. First of all, the command is not very intuitive. How do we know that we have to pass the limit to the command? For that, the Symfony Console offers us the Question helper.

Question Helper

The Question helper provides functionality to ask the user for more information. This way we can interactively collect information for the execution of our commands.

Let’s change our command to, instead of receiving a limit of execution through the command execution prompt, ask the user for a limit. For that, the question helper has a single method: ask(). This method receives as arguments an InputInterface, an OutputInterface and a question.

Let’s change the FizzCommand.php file so it looks like this:

<?php

namespace FizzBuzz;

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

use FizzBuzz\Fizzbuzz;

class FizzCommand extends Command{

    protected function configure(){
        $this->setName("FizzBuzz:FizzBuzz")
                ->setDescription("Runs Fizzbuzz");
    }

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

        $fizzy = new FizzBuzz();

        $helper = $this->getHelper('question');
        $question = new Question('Please select a limit for this execution: ', 25);
        $limit = $helper->ask($input, $output, $question);

        $result = $fizzy->firstNFizzbuzz($limit);
    }
}

We no longer expect an argument on the configure() method. We instantiate a new Question with a default of 25 and use it on the ask() method we talked about earlier.

Now we have an interactive command that asks for a limit before executing Fizzbuzz.

The question helper also gives us functionality to validate the answers. So let’s use it to make sure the limit is an integer.

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

        $fizzy = new FizzBuzz();

        $helper = $this->getHelper('question');
        $question = new Question('Please select a limit for this execution: ', 25);

        $question->setValidator(function ($answer) {
            if (!is_numeric($answer)) {
                throw new \RuntimeException('The limit should be an integer.');
            }
            return $answer;
        });

        $question->setNormalizer(function ($value) {
            return $value ? trim($value) : '';
        });

        $question->setMaxAttempts(2);
        $limit = $helper->ask($input, $output, $question);

        $result = $fizzy->firstNFizzbuzz($limit);
    }

Not only are we making sure that our limit is an integer by using the setValidator() function, we are also normalizing the input in case the user inserts some blank spaces and also setting the maximum amount of attempts permitted to two.

The question helper offers a lot more functionality like letting the user choose from a list of answers, multiple answers, hiding the user answer, and autocompletion. The official documentation has a lot more information on that.

Tables

Another very useful function provided by the console component is the possibility to display tabular data.

To display a table we need to use the Table class; set the headers and rows, and finally render the table. This can be very useful when it comes to showing structured data. Let’s imagine we want to create a command to show the conversions for some metric systems.

Let’s add MetricsCommand.php to our new php file.

<?php

namespace Metric;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Helper\Table;

class MetricsCommand extends Command{

    protected function configure(){
            $this->setName("Metrics")
                ->setDescription("Inches to centimeters table.");
       }

    public function execute(InputInterface $input, OutputInterface $output){    
            $table = new Table($output);
            $table
                ->setHeaders(array('Inches', 'Centimeters'))
                ->setRows(array(
                    array('1', '2.54'),
                    array('5', '12.7'),
                    array('10', '25.4'),
                    array('50', '127'),
            ))
        ;
        $table->render();
    }
}

And our new console file:

#!/usr/bin/env php

<?php 

require_once __DIR__ . '/vendor/autoload.php'; 

use Symfony\Component\Console\Application; 
use Metric\MetricsCommand;

$app = new Application();
$app->add(new MetricsCommand());
$app->run();

It’s a very simple command: it renders a table with some values converted from inches to centimeters. If we run our command using ./console Metrics, the result will be something like this:

Our execution result

The Table class also offers us different separator styles for our tables. Check this page if you want to know more. .

Progress Bars

While questions and tables can be very useful, the most important element might be the progress bar. Progress bars give us feedback about the execution of the command and allow us to have a clear view of how long we might have to wait for an operation to finish.

Progress bars are essential for longer running commands. To use them, we need the ProgressBar, pass it a total number of units (if we actually know how many units we expect) and advance it as the command executes.

A simple command with a progress bar may look like this:

<?php

namespace Progress;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Helper\ProgressBar;

class ProgressCommand extends Command{

    protected function configure(){
        $this->setName("Progress")
            ->setDescription("Check Console componenet progress bar.");
       }

    public function execute(InputInterface $input, OutputInterface $output)
    {    
        $progress = new ProgressBar($output);
        $progress->start();

        $i = 0;
        while ($i++ < 50) {
            usleep(300000);
            $progress->advance();
        }

        $progress->finish();
    }
}

And the respective console:

#!/usr/bin/env php

<?php 

require_once __DIR__ . '/vendor/autoload.php'; 

use Symfony\Component\Console\Application; 
use Progress\ProgressCommand;

$app = new Application();
$app->add(new ProgressCommand());
$app->run();

This a very simple command. We set up the bar and loop through a sleep() function. The final output will look like this:

Progress bar example

More information on progress bars can be found in the official documentation.

Customizing Our Progress Bar

Customizing progress bars can be useful to provide extra information while the user waits.

By default, the information shown in the progress bar depends on the level of verbosity of the OutputInterface instance. So, if we want to show different levels of information we can use the setFormat() method.

$bar->setFormat('verbose');

The built-in formats are: normal, verbose, very_verbose and debug.

If we use use normal format for example, the result will look like this:

Progress bar, normal format

We can also set our own format.

The progress bar is a string that’s composed of different specific placeholders. We can combine those specific placeholders to create our own progress bars. The available placeholders are: current, max, bar, percent, elapsed, remaining, estimated, memory and message. So if, for instance, we wanted to copy the exact same default progress bar, we could use the following:

$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');

There’s a lot more to customizing progress bars – read about it here.

Calling a command inside a command

Another very useful feature to have is the ability to run a command inside a command. For example, we might have a command which depends on another command to successfully run, or a succession of commands we might want to run in a sequence.

For example, imagine we wanted to create a command to run our fizzbuzz command. We would need to create a new command inside our /src folder and inside the execute() method, have the following:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $command = $this->getApplication()->find('FizzBuzz:FizzBuzz');
    $returnCode = $command->run();
}

Since our FizzBuzz command doesn’t receive any arguments, that would be enough. In case our command needed arguments we would have to create an array of arguments and use the ArrayInput class to pass them.

Other than that it’s all about using the find() method with our command name to find and register our command.

Color and Style

Coloring and styling the output can be useful for alerting or informing the user about something in the command’s execution. For that, we just need to add the following tags to our writeln() method, just like the following:

// green text
$output->writeln('<info>Output here</info>');

// yellow text
$output->writeln('<comment>Output here</comment>');

// black text on a cyan background
$output->writeln('<question>Output here</question>');

// white text on a red background
$output->writeln('<error>Output here</error>');

There’s also the option to define our own styles using the OutputFormatterStyle class:

$style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink'));
$output->getFormatter()->setStyle('fire', $style);

$output->writeln('<fire>foo</fire>');

More information on styling the output can be found here.

Summing up

From styling to helpers, we saw a lot of functionality that the Symfony console provides out of the box. After today, there’s absolutely no excuse to have badly documented command line tools!

Which helpers and components of the Console do you frequently use? How do you start your CLI tools? Is the Symfony Console enough for you, or do you prefer an alternative?

Frequently Asked Questions (FAQs) about Symfony Console

What are the key differences between Symfony Console and other PHP console applications?

Symfony Console is a component of the Symfony PHP framework that provides a way to create command-line interfaces (CLI). Unlike other PHP console applications, Symfony Console offers a structured and object-oriented approach to building CLI applications. It provides a set of classes to define commands, handle input and output, and manage the application’s lifecycle. Additionally, Symfony Console supports color formatting, progress bars, tables, and other advanced console features, which are not commonly found in other PHP console applications.

How can I create a custom command in Symfony Console?

Creating a custom command in Symfony Console involves extending the Command class and implementing the configure() and execute() methods. The configure() method is used to define the command name, arguments, and options, while the execute() method contains the logic of the command. Once the command class is created, it can be added to the application using the add() method.

How can I handle input and output in Symfony Console?

Symfony Console provides the InputInterface and OutputInterface to handle input and output in a command. The InputInterface provides methods to get the command arguments and options, while the OutputInterface provides methods to write to the console. You can also use the InputArgument and InputOption classes to define the command arguments and options.

How can I use helpers in Symfony Console?

Helpers in Symfony Console are classes that provide additional functionality for commands. They can be accessed using the getHelper() method of the command. Symfony Console includes several built-in helpers, such as the QuestionHelper for interactive input, the ProgressBarHelper for progress bars, and the TableHelper for tabular data.

How can I test commands in Symfony Console?

Symfony Console provides the CommandTester class to test commands. The CommandTester class allows you to execute a command with specific input and capture the output. You can then make assertions on the output to verify the command’s behavior.

How can I handle errors in Symfony Console?

Errors in Symfony Console can be handled using exceptions. If an error occurs during the execution of a command, you can throw an exception. Symfony Console will catch the exception and display an error message to the user.

How can I use the Symfony Console component outside of the Symfony framework?

The Symfony Console component is a standalone component, which means it can be used outside of the Symfony framework. You can install it using Composer and use it to build CLI applications in any PHP project.

How can I customize the appearance of the console output?

Symfony Console provides several ways to customize the appearance of the console output. You can use color codes to change the text color, format codes to change the text style, and tags to create sections. You can also use the ProgressBar and Table classes to create progress bars and tables.

How can I create a console application with multiple commands?

To create a console application with multiple commands, you can add multiple command classes to the application. Each command class should extend the Command class and implement the configure() and execute() methods. You can then use the add() method of the application to add the commands.

How can I use the Symfony Console component to automate tasks?

The Symfony Console component can be used to automate tasks by creating commands that perform specific tasks and running these commands from the command line or from a script. You can also schedule commands to run at specific intervals using a task scheduler like cron.

Claudio RibeiroClaudio Ribeiro
View Author

Cláudio Ribeiro is a software developer, traveler, and writer from Lisbon. He's the author of the book An IDE Called Vim. When he is not developing some cool feature at Kununu he is probably backpacking somewhere in the world or messing with some obscure framework.

BrunoScliconsoleOOPHPPHPsymfony2
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week