PHP - - By Claudio Ribeiro

Symfony Console Beyond the Basics – Helpers and Other Tools

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?

Sponsors