Console Wars – PHP CLI Libraries
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:
- The Symfony console component (symfony/Console)
- The Hoa console (hoa/console)
- The Webmozart console (webmozart/console)
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!