PHP
Article
By Nicola Pietroluongo

Console Wars – PHP CLI Libraries

By Nicola Pietroluongo

I have always been a big fan of console commands and I try to provide a command line interface (CLI) as much as possible in most of my PHP projects.

In this article, I’ll briefly compare three PHP console command libraries:

Image of terminal screen

Origin Stories

The Symfony console is the oldest and the most popular one, used in many projects (and obviously part of the Symfony framework). With dozens of contributors, it became the first choice for many developers.

Hoa is a modular, extensible and structured set of PHP libraries that includes the Hoa console. It aims to be a bridge between industrial and research worlds, and this makes that project quite interesting.

The Webmozart console is the newest project, wants to be easier, test friendly and add new functionality on top of the Symfony console.

Dependencies, Size, and Complexity

The Symfony console has only suggested dependencies, as opposed to the Hoa console library that depends on some Hoa project libraries. The Webmozart project, too, directly depends on the Symfony console.

The Hoa console has the smallest number of LOC (Logical Lines of Code) ~1397, followed by the Symfony console ~2226 and the Webmozart ~3126 (without dependencies).

In order to have a rough indicator of the complexity of these projects, below is some data from their PHPLOC analysis*:


Description Symfony Hoa Webmozart
Cyclomatic Complexity
Average Complexity per LLOC 0.37 0.36 0.26
Average Complexity per Class 14.73 25.14 8.84
Average Complexity per Method 2.55 3.38 1.99
Dependencies
Global Accesses 3 20 1
Attribute Accesses 807 217 1285
Method Calls 1103 324 1320

*The analysis is performed only in the main source directory, excluding the test folder when present.

Practical example

Description

To have an overview of each library’s functionality and see the code in action, let’s write a business feature to describe a usage example:

Feature: I want to output a message to several people.
	The message should be passed via the `--message` option and should be optional (default="Hello"),
	the message should be followed by two or more names,
	the message should be coloured with `--color=` (default="white") and/or in uppercase with `--up` (default=lowercase).

The final console call should be something like:
somemsg --message='Good Morning' Nicola Bruno --color=green --up
and the output should be:

GOOD MORNING NICOLA AND BRUNO

Implementation

First, we need to define a PHP Message, used in every console implementation, to handle the example.

Below is some pretty straightforward code:

class Message
{
    /**
     * Construct the class and initializes the properties.
     * 
     * @param        $names
     * @param string $message
     * @param bool   $uppercase
     */
    public function __construct($names, $message="Hello", $uppercase=false)
    {
        $this->names = implode(' and ', $names);
        $this->message = $message;
        $this->uppercase = $uppercase;
    }

    /**
     * Generates the output.
     * 
     * @return string
     */
    public function getMessage()
    {
        $output =  $this->message . ' ' . $this->names;
        if ($this->uppercase) {
            $output = strtoupper($output);
        }

        return $output;
    }
}

Symfony console

In order to create a console command in Symfony, it’s necessary to:

  • Create the command class
    – Configure the arguments and options
    – Write the logic
  • Create the application

Create The command

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

class MessageCommand extends Command
{
    /**
     * Configures the argument and options
     */
    protected function configure()
    {
        $this
            ->setName('demo:msg')
            ->setDescription('Simple message delivery')
            ->addArgument('names', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Who do you want to message?')
            ->addOption('message', null, InputOption::VALUE_REQUIRED, 'Set the message', 'Hello')
            ->addOption('up', null, InputOption::VALUE_NONE, 'Set the output in uppercase')
            ->addOption('color', null, InputOption::VALUE_REQUIRED, 'Which colors do you like?', 'white')
        ;
    }

    /**
     * Executes the logic and creates the output.
     * 
     * @param InputInterface $input
     * @param OutputInterface $output
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $names = $input->getArgument('names');
        $message = $input->getOption('message');
        $uppercase = $input->getOption('up');
        $color = $input->getOption('color');

        $message = new Message($names, $message, $uppercase);
        $messageString = $message->getMessage();
        $coloredMsg = '<fg='.$color.'>'.$messageString.'</fg='.$color.'>';

        $output->writeln($coloredMsg);
    }
}

The configure method is used to set up the arguments and options for the command.

The addArgument method can receive the following parameters:
addArgument($name, $mode, $description, $default)


type name description
string $name The argument name
int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
string $description A description text
mixed $default The default value (for InputArgument::OPTIONAL mode only)

The addOption can receive the following parameters:
addArgument($name, $shortcut, $mode, $description, $default)


type name description
string $name The option name
string $shortcut The shortcut (can be null)
int $mode The option mode: One of the InputOption::VALUE_* constants
string $description A description text
mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE)

There are three options available to color the output:

  • Use a preset tag (es: $output->writeln('<info>foo</info>'); for green output)
  • Define a style using the OutputFormatterStyle class
  • Set the color inside the tag name es:
// red text on a cyan background
 $output->writeln('<fg=red;bg=cyan>foo</fg=red;bg=cyan>');

The available foreground and background colors are: black, red, green, yellow, blue, magenta, cyan and white. The available options are: bold, underscore, blink, reverse and conceal.

More information in the official Symfony documentation.

Note: by default, the Windows command console doesn’t support output coloring. You’ll need (and should install) Git tools or another more advanced command console.

Create The application

After the configuration and the execution, we’re almost done. The last step is creating a PHP file to run the command.

//file myconsole.php

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

use MessageCommand;
use Symfony\Component\Console\Application;

$application = new Application();
$application->add(new MessageCommand());
$application->run();

Console call example:

php myconsole.php demo:msg Nicola Bruno --message='Good Morning' --color=blue --up

The Symfony console also automatically provides the output helper with the --help argument.

Hoa console

The Hoa console follows a less structured approach to configuring the console command.

That process consists of the following steps:

  • Parse the command
  • Get options and input
  • Execute the logic

Parse the command

/**
 * $argv contains an array of all the arguments passed to the script,
 * the first argument is always the name of the PHP file.
 * the Hoa Parser->parse method accept a string in input, so it's necessary to convert the $argv array in a string without the first argument as below.
 */
unset($argv[0]);
$command = implode(' ', $argv);

$parser = new Hoa\Console\Parser();
$parser->parse($command);

//options definition
//['longname', TYPE, 'shortname']
$options = new Hoa\Console\GetOption(
    [
        ['up', Hoa\Console\GetOption::NO_ARGUMENT, 'u'],
        ['message', Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'm'],
        ['color', Hoa\Console\GetOption::OPTIONAL_ARGUMENT, 'c']
    ],
    $parser
);

Get options and inputs

//definition of default values
$uppercase = false;
$message = "Hello";
$color = "white";

$names = $parser->getInputs();

//The following while with the switch will assign the values to the variables.
while (false !== $shortName = $options->getOption($value)) {
    switch ($shortName) {
        case 'u':
            $uppercase = true;
            break;
        case 'm':
            $message = $value;
            break;
        case 'c':
            $color = $value;
            break;
    }
}

Execute the logic

$message = new Message($names, $message, $uppercase);
$messageString = $message->getMessage();

Hoa\Console\Cursor::colorize('fg('.$color.')');
echo $messageString;
Hoa\Console\Cursor::colorize('fg(white)'); //reset the cursor to default white

For coloring the output, it’s possible to change the Cursor color.

The Hoa console supports a wide range of colors.
The color can be set by name (black, red, green, yellow…), by number (from 0 to 256 representing the 264 color palette) or by hexadecimal code #rrggbb, example:
Hoa\Console\Cursor::colorize('fg(yellow) bg(#932e2e) underlined');

The basic usage in the example doesn’t provide an automatic helper output, and is not strongly OOP oriented but extending the Hoa\Console\Dispatcher\Kit (requires hoa/dispatcher) could add more flexibility (more information in the official documentation)

The command can be called with:

php message.php  -u --message=Hello --color=green Nicola Bruno

One of the strongpoints of the Hoa console is that it provides additional API classes to manipulate important elements, supporting the different terminal profiles:

  • The Cursor (move, clear, show, colorize, …)
  • The Mouse (listening to mouse action)
  • The Window (setSize, scroll, minimize, …)
  • The terminal line with Readline (history, autocompletion, etc)

Webmozart console

The Webmozart console command creation workflow consists of:

  • configuring the argument and options
  • writing the logic
  • creating the application

Webmozart’s console follows an approach similar to the Symfony Console, but with a clear separation between the configuration and the logical execution.

Configuration

use Webmozart\Console\Api\Args\Format\Argument;
use Webmozart\Console\Api\Args\Format\Option;
use Webmozart\Console\Config\DefaultApplicationConfig;

/**
 * Configuration of arguments and options
 */
class MsgApplicationConfig extends DefaultApplicationConfig
{
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('msg')
            ->setVersion('0.1')
            ->beginCommand('msg')
                ->setDescription('Show a nice message')
                ->setHandler(new MsgCommandHandler())
                ->addArgument('names', Argument::MULTI_VALUED | Argument::REQUIRED, 'Who do you want to message?')
                ->addOption('message', null, Option::OPTIONAL_VALUE, 'Set the message', 'Hello')
                ->addOption('up', null, Option::NO_VALUE, 'Set the output in uppercase')
                ->addOption('color', null, Option::REQUIRED_VALUE, 'Which colors do you like?', 'white')
            ->end()
        ;
    }
}

Logic

use Webmozart\Console\Api\Args\Args;
use Webmozart\Console\Api\IO\IO;

/**
 * Handling the command logic
 */
class MsgCommandHandler
{
    public function handle(Args $args, IO $io)
    {
        //gets the argument and option	
        $names = $args->getArgument('names');
        $message = $args->getOption('message');
        $uppercase = $args->getOption('up');
        $color = $args->getOption('color');
        
        $message = new Message($names, $message, $uppercase);
        $messageString = $message->getMessage();
        
        $coloredMsg = '<fg='.$color.'>'.$messageString.'</fg='.$color.'>';
        
        $io->writeLine($coloredMsg);
    }
}

The strong separation of configuration and logic allows more flexibility for easy testing and for a project that will grow with additional commands.

Other advantages of the Webmozart’s console are:

  • sub commands support:

    php mycommand.php msg send --arg1 --arg2
    	 php mycommand.php msg receive --someoptions=somevalue
  • support for manpage documentation (like with “git help remote”)

  • adapters for the Symfony console (to use Symfony’s classes like ProgressBar)

Creating The application

The application file to run the command is similar to the Symfony one:

require 'vendor/autoload.php';
use Webmozart\Console\ConsoleApplication;

$cli = new ConsoleApplication(new MsgApplicationConfig());
$cli->run();

Console call:

php myconsole.php msg --message='Good Morning' Nicola Bruno --color=blue --up

Final Thoughts

Each console covered above provides different functionalities for different use types and user preferences.

  • The Symfony console is well tested, robust, with good documentation and features to solve most of the average use cases.
  • The Hoa console is more industry oriented, perfect for manipulating the terminal environment (mouse, cursor, window, and so on).
  • The Webmozart console is new (there will be a stable release soon) but it’s very useful for handling projects that tend to grow to large sizes.

Do you use any of them regularly? If so, which one? Why? What would you say the pros and cons of each are? Do you have other contenders to suggest? Let us know!

  • ismaail

    Thanks for the post.

    In Symfony code example, the last bloc (line 9)
    your created new class “GreetCommand” instead of “MessageCommand”.

  • tumichnix

    Nice post!

  • I have used https://github.com/auraphp/Aura.Cli , which is nice. There are a few features that is missing in Aura which you need to use / build your own like Tables, hidden text for passwords, choice questions etc. So in cases I need more features I used to go with Symfony console. It have a large user base and stable releases makes me / client confident using it. I do love Hoa for some of the features it offers.

  • jubianchi

    Hi !

    I’m @jubianchi:disqus , one of Hoa’s contributor. Some time ago I worked on the Console library

    and I think that adding a bit more content about it could be good.

    The article is very good at comparing the libraries on a common use case but what we

    don’t clearly see here is the goal of each library. In fact symfony/console and webmozart/console have common goals: providing a clear and simple high-level abstraction to build console application. hoa/console is a bit different here: it’s more a low-level building block meant to be the foundation of abstraction

    such as the two others.

    As you said in your article, hoa/console “is not strongly OOP oriented”, in fact, it’s more like a
    static library providing tools
    to work with windows, cursor and other things. Everything is based on tput (https://en.wikipedia.org/wiki/Tput ) which is a very strong standard.

    As an example of how this library can be used to build (or extend) high-level abstraction, you can take a look at hoathis/symfony-console-bridge (https://packagist.org/packages/hoathis/symfony-console-bridge ) which provides a bridge to the Symfony world and the hoathis/symfony-console-bundle (https://packagist.org/packages/hoathis/symfony-console-bundle ) which provides a bundle extending the Symfony native console. With this approach, we are able to provide a glue between the framework and the library which will always be framework agnostic. We can then wire our tools to any API, be it provided by Symfony or Zend or any other vendor, which are more user-friendly.

    The goal of hoa/console, and also other Hoa libraries, is really to provide low-level building blocks. Using them directly to build apps might in fact not be as easy as using other bundles/frameworks/… but in some case, it’s more powerfull because we favor the completeness over the simplicity.

    Here are some other resources that might be interesting to check to see what hoa/console can do:

    * http://hoa-project.net/En/Awecode/Console-readline.html
    * https://github.com/hoaproject/Contributions-Symfony-ConsoleBridge/blob/master/README.md
    * http://mnt.io/P/2015-01-04_Control_the_terminal_the_right_way.html

    By the way, thanks for this great post !

    • Bernhard Schussek

      Hi Julien! I didn’t know too much about Hoa console, but I was looking for some PHP library with cursor and screen manipulation abilities. This really looks exciting! I’ll look into integrating your library into Webmozart console. Thank you for the great work :)

  • There’s one more, less known but also worth to mention: ZF-Console, from Zend Framework folks: https://github.com/zfcampus/zf-console

  • CTN

    way too complex. i’d rather learn how to do this without using some complex framework that creates complexity.

    just give me some raw php code.

  • Hermann Herz

    I want to mention CLImate here also, see http://climate.thephpleague.com

  • Jose Manuel Mujica Puentes

    Nice post Nicola, Thank you

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