I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 637 posts at DZone. You can read more from them at their website. View Full User Profile

The Wheel: Symfony Console

03.11.2013
| 4795 views |
  • submit to reddit

I recognize I may be biased towards writing custom solutions due to my focus on complex domains such as transportation and payments, where new code is needed to provide a competitive advantage. In this new series, The Wheel, I explore open source projects from the PHP world in order to build and provide you a knowledge of existing tools: let's fight the Not Invented Here syndrome together, and avoid reinventing the wheel.

This week, I'm going to explore the Symfony Console.

The Symfony Components

Symfony is one of the most popular open source PHP frameworks on the market. The Symfony Components, however, are loosely coupled projects that can be reused as a library outside of an application based on Symfony.

The component this article explores is Console (symfony/console on Packagist and GitHub), dedicated to quickly build console applications; I recently contributed to the PHPUnit wrapper Paratest which is built with it. As most (or all?) of the Symfony Components, it has no dependencies; even if they were present, I would took care of that with Composer (subject of our next article).

The pros

Let's evaluate why you should adopt the Symfony Console for your scripts or command line application. First of all, you should decide if the command line user interface is a strategic or utility aspect of your project: in all the cases where there is no competitive advantage in it, outsourcing most of the code to a library is cost-effective. If users decide to switch to your application for other reasons than the interface, you're better off saving time in this section of the code and dedicate it to your Core Domain.

You get functionalities for free. Given this code from the documentation:

class GreetCommand extends Command
{
  protected function configure()
  {
      $this
          ->setName('demo:greet')
          ->setDescription('Greet someone')
          ->addArgument(
              'name',
              InputArgument::OPTIONAL,
              'Who do you want to greet?'
          )
          ->addOption(
              'yell',
              null,
              InputOption::VALUE_NONE,
              'If set, the task will yell in uppercase letters'
          );
  }

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

And you get almost everything you ever need from a script: commands, arguments and parameters.

[08:11:05][root@onebipdev:~/console_example]$ ./app.php demo:greet
Hello
[08:11:11][root@onebipdev:~/console_example]$ ./app.php demo:greet Giorgio
Hello Giorgio
[08:11:14][root@onebipdev:~/console_example]$ ./app.php demo:greet --yell Giorgio
HELLO GIORGIO

Actually, you can add multiple commands other than demo:greet, by configuring objects:

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

And I like very much that Symfony Components are champions of the PSR-0 standard and you can build functionality by composing objects explicitly instead of placing scripts and classes inside special folders like in many first-generation frameworks.

You also get for free an interface with some usability. Here's what `./app.php`  run without any selected command outputs:

$ ./app.php
Console Tool

Usage:
  [options] command [arguments]

Options:
  --help  -h Display this help message.
  --quiet  -q Do not output any message.
  --verbose  -v Increase verbosity of messages.
  --version  -V Display this application version.
...

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

The standard styling of this output is characteristic of applications built with the Symfony Console, and is nice for PHP developers to see a familiar help screen.

Error management is also included in the price. In case of an uncaught exception, the process does not terminate with a stack trace but instead the exception is displayed:

$ ./app.php demo:greet
  [RuntimeException]   
  Sorry, the thread has gone askew on the kettle  
demo:greet [--yell] [name]
(blanks redacted for space)

The (potential) cons

I got to know better the Symfony Console in less than an hour thanks to the great documentation. However, there is always a trade-off between custom and open source code: something has to be sacrificed in order to achieve these functionalities for free.

The first trade-off is simplicity vs. size: for small applications and one-shot scripts grabbing 10,839 lines of code is a large overhead, even if Composer makes it easy. getopt() is obscure but only 6-letters away; as the number of commands and options increase, Symfony Console scales better.

The second trade-off is the classic one of inversion of control: you give up control of the main flow of your application to a framework where you have to fill in the missing pieces. This level of indirection is helpful for the functionalities it provides, but adds complexity to all your stack traces and profiling outputs.

The last trade-off is that you have to agree on the decisions taken by opinionated libraries; for example, --help is a standard command that you will have to accept in your script precisely in this format. There's nothing wrong in principle either with supporting help and list or not, but choosing the library will make the decision for you.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)