PHP
Article

Symfony2 Console: Getting Started with Console Helpers

By Nicolas Scolari

Lately, I’ve been spending a significant amount of time working with the Symfony 2 Console Component in order to process different tasks, especially cronjobs. In this tutorial, I’ll share my experiences and we’ll give some extra love to the console helpers, which provide us with a large collection of handy functions. There are a lot of reasons to create console commands in your projects: sending emails, exporting/importing data, creating users, and so on.

Digital information

Introduction

First, I recommend that you read this SitePoint article by Daniel Gafitescu. He explains how to get started with the Symfony 2 Console Component with a simple example: generate all Fibonacci numbers between two given integers. Daniel also introduces essential concepts that we won’t go through today, so I highly suggest you have a look before moving on.

By the end of this post, we want to be able to create a basic console command to generate some output – any output will do – only the way to getting there is important. Near the end, we’ll discover some console helpers in order to create some nice interactions between users and the interface.

Installation from scratch (in a non-Symfony environment)

As you probably know, you are free to install the Console in the environment of your choice. As a component, it can be used anywhere you need it and not necessarily inside a Symfony 2 application.

  1. Create a new directory with the name we want (e.g. Project/)

  2. Add a composer.json file at the root level and update it with the chosen dependencies:

    {
    	    "require": {
    	        "php" : ">=5.5.0",
    	        "symfony/console": "~2.6"
    	    },
    	
    	    "autoload": {
    	        "psr-4": {
    	            "Cli\\": "src/Cli/"
    	        }
    	    }
    	}

    Then, run composer update.

  3. Now that we have our dependencies ready to run, we’ll need to set up our actual console executable: create a file called console in the project root (I prefer to include it in a bin/ folder but you can name it whatever you want and place it wherever), and give it the following content:

<?php

require_once 'vendor/autoload.php'; // use path relative to current folder - i.e. ../vendor/autoload.php if you placed it inside /bin

use Symfony\Component\Console\Application;

$console = new Application('CLI with the Symfony2 Console Component', '0.1.0');
$console->run();

It’s time to check if everything’s set up right. Let’s run our new command:

php bin/console

The output should be something like this:

CLI with Symfony2 Console Component version 0.1.0

Usage:
 command [options] [arguments]

Options:
 --help (-h)           Display this help message
 --quiet (-q)          Do not output any message
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version
 --ansi                Force ANSI output
 --no-ansi             Disable ANSI output
 --no-interaction (-n) Do not ask any interactive question

Available commands:
 help   Displays help for a command
 list   Lists commands

The basic (raw) command

Getting started with building our CLI tool takes 3 steps:

  1. Creating a new folder (e.g. src/) containing a generic directory (e.g. Cli/) for all the business logic.
  2. Adding a Command/ directory inside Cli/ to make command(s) available.
  3. Building a BasicCommand.php class.

Note that the file name must always end in Command.php.

Project
    |-- bin/
    |    |-- console
    |-- src/
    |    |-- Cli/
    |         |-- Command/
    |              |-- BasicCommand.php
    |-- vendor/
    |-- composer.json
    |-- composer.lock
    |-- composer.phar

The class

<?php
// src/Cli/Command/BasicCommand.php

namespace Cli\Command;

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

class BasicCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('say:hello')
            ->setDescription('Basic command to generate something')
        ;
    }

    /**
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('<comment>Hello SitePoint Folks!</comment>');
    }
}

Now we have to attach this new class to our console application. To do this, we have to edit our bin/console file and add the following code:

<?php

require_once 'vendor/autoload.php';

use Symfony\Component\Console\Application;

$console = new Application('CLI with the Symfony2 Console Component', '0.1.0');

// This is the relevant addition
$console->addCommands(array(
    new Cli\Command\BasicCommand(),
));
// End of relevant addition

$console->run();

Testing the Command

# run the command
php bin/console say:hello

# It should display the following result
Hello SitePoint Folks!

The Helpers

The Question Helper

This helper is used to ask and collect some information from the user.

Note: you may have already heard about the Dialog Helper. It is now deprecated and it is strongly recommended you use the Question Helper from Symfony 2.5 onwards.

This is how we use it inside a command:

<?php
// ...
$helper = $this->getHelper('question');
// ...

You should be able to imagine all sorts of questions: a simple confirmation (yes/no), information, a choice inside a list of answers etc.

Here are some basic examples:

Prompting for confirmation before moving on

<?php
// ...
use Symfony\Component\Console\Question\ConfirmationQuestion;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = $this->getHelper('question');

        $question = new ConfirmationQuestion("Do you confirm this action? (yes/no) ", false);

        $answer = $helper->ask($input, $output, $question);

        if ($answer) {
            // ... next step
        } else {
            return;
        }
    }
// ...

Asking for the user’s language

<?php
// ...
use Symfony\Component\Console\Question\Question;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = $this->getHelper('question');

        $question = new Question("What is your language?\n", 'english');

        $language = $helper->ask($input, $output, $question);

        $output->writeln('You chose '.$language);

        // ... do something
    }
// ...

Asking the user to pick from a predefined set of answers

<?php
// ...
use Symfony\Component\Console\Question\ChoiceQuestion;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = $this->getHelper('question');

        $question = new ChoiceQuestion(
            "What is your favorite website",
            ['sitepoint.com', 'google.com', 'twitter.com'],
            0
        );

        $choice = $helper->ask($input, $output, $question);

        $output->writeln('You chose: '.$choice);

        // ... do something
    }
// ...

In each case, the process is similar:

  1. We have to import the appropriate classes:

    use Symfony\Component\Console\Question\ConfirmationQuestion;
    	// or
    	use Symfony\Component\Console\Question\Question;
    	// or
    	use Symfony\Component\Console\Question\ChoiceQuestion;
  2. We call the helper: $helper = $this->getHelper('question');

  3. We instantiate a new object:

    new ConfirmationQuestion();
    	// or
    	new Question();
    	// or
    	new ChoiceQuestion();
  4. Finally, we use the ask() method in order to display the question.

Note: the second parameter for ConfirmationQuestion() and Question() is the default value if nothing is entered. Concerning ChoiceQuestion(), it appears at the third position.

The Table class

Because it could sometimes be relevant to display data in a pleasant format, the console offers a Table class, initially known as Table Helper (deprecated since Symfony 2.5). We use it thusly:

<?php
// ...
use Symfony\Component\Console\Helper\Table;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $table = new Table($output);
        $table
            ->setHeaders(['Country', 'City', 'Population'])
            ->setRows([
                ['USA', 'New-York', '8 405 837'],
                ['France', 'Paris', '2 249 975'],
                ['Germany', 'Berlin', '3 517 424'],
                ['Australia', 'Sydney', '4 757 083'],
                ['England', 'London', '8 416 535']
            ])
        ;
        $table->render();
    }
// ..

console

As you can see, nothing magical here. We instantiate a new Table object and set the Headers and Rows with the data. The last step renders the table with the method render().

For those who want to go further and have fun with designing tables, you can find a set of methods here available for customization. You just have to add an extra use statement: Symfony\Component\Console\Helper\TableStyle and override the style with an instantiation of TableStyle().

<?php
// ...
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $style = new TableStyle();
        $style
            ->setVerticalBorderChar('<fg=red>|</>')
            ->setHorizontalBorderChar('<fg=red>-</>')
            // ... add more custom
        ;

        $table = new Table($output);
        $table
            ->setHeaders(['First Name', 'Last Name', 'Date of Birth'])
            ->setRows([
                ['John', 'Doe', '08-08-1988']
                // ... add rows
            ])
        ;
        $table
            ->setStyle($style)
            ->render()
        ;
    }
// ...

console2

The Progress Bar

Like the others, this helper has been redesigned in version 2.5 of Symfony, as an alternative to the Progress Helper. You may have guessed that this feature gives us the opportunity to display progress information in an elegant way. In this section, I will cover the basics so you get familiar enough with the tool. Actually, this very topic could be worth an entire article given how customizable it is.

Here is some skeleton code, the minimum required:

<?php
// ...
use Symfony\Component\Console\Helper\ProgressBar;
// ...
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $progress = new ProgressBar($output, 10);

        $progress->start();

        $i = 0;
        while ($i++ < 10) {
            // ... in order to simulate the progression, I add a `usleep()` method
            usleep(rand(100000, 1000000));

            $progress->advance();
        }

        $progress->finish();
    }
// ...
  1. Import the class: use Symfony\Component\Console\Helper\ProgressBar;
  2. Instantiate a new ProgressBar object with the number of steps as second argument;
  3. Launch the start() method to start the progress output;
  4. Create a loop with the steps as max value;
  5. Advance the progress as the command executes with the advance() method;
  6. Launch the finish() method to finish the progress output.
# run the command
php app/console demo:sitepoint

# the ProgressBar at the start:
 0/10 [>---------------------------]   0%

# the ProgressBar at an intermediate level:
 6/10 [================>-----------]  60%

# the ProgressBar at the end:
10/10 [============================] 100%

Note that the progress bar only works if your platform supports ANSI codes; on other platforms, no output is generated.

To explore the ProgressBar into more depth, take a look at the class docs. Enjoy!

Final Thoughts

I hope you see the benefit of Helpers in designing interaction between the user and machine when developing console apps. Do you have any favorite aspects of console development? How about favorite helpers or their customized versions? Let us know!

By the way, for the Laravel aficionados among you, I invite you to read this SitePoint article from Younes Rafie which introduces you to building a CSS minification console command.

Comments
TaylorRen

It will be great if we see a real sample on Progress bar, like when generating a bundle, or copying a big file. How will it work? By callbacks?

swader

Yep, callbacks are the way to go. I would love a more in depth post about that, though, /hint hint

s_stok

The Symfony installer shows a great example smile

Guzzle Download progress with the Progressbar.

s_stok

I would actually recommend to have a look at https://github.com/webmozart/console which provides an alternative architecture for the Symfony Console command (Compatible with Symfony using Adapters).

Especially when building Console based applications as the Symfony Console components makes it much harder to properly separate service logic and keep things testable. (Thats the problem were are currently having for Gush)

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.