Stylish and Standard Console Output with Symfony Style

Even if you don't use any component from Symfony or even installed one, you can use this trick in your PHP CLI App.

It's simple, provides standard and makes your output look like a design from Apple - useful and nice at the same time.

We want to report various states in PHP CLI Apps. Success message on the finish, errors message in case of failure or just simple note so users know that command is not stuck but working.

Too Many Ways to Do 1 Thing

You can use plain PHP like in PHP_CodeSniffer:

<?php

try {
    // code
} catch (Exception $exception) {
    echo $exception->getMessage();
    return $exception->getCode();
}

There is also a advanced use of native OutputInterface in command like PHP CS Fixer:

<?php

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

final class SomeCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // ...

        $output->write('Working on it!');

        // ...
    }
}

The advantage of these approaches is they cannot be simpler and they're ready to be used. I bet everyone can use echo 'DONE';:

The second approach is not as easy, but if you're in a Symfony Command class using PHPStorm all you have to do is hit Ctrl + Space on an $output variable. And in their time they were good enough.


But we want more than a plain text. If websites can have CSS, colors, and pictures, why not the CLI output?


But it's not about colors, it's about UX. Green and red lines instead of white on black spaghetti like on the first image.


Last but not least, Symfony $output has few predefined styles:

<?php

// green text
$output->writeln('<info>foo</info>');

// white text on a red background
$output->writeln('<error>foo</error>');

And also some colors and cool stuff:

// green text
$output->writeln('<fg=green>foo</>');

// bold text with underscore
$output->writeln('<options=bold,underscore>foo</>');

Which one do you like so far? So many colors, so many options... maybe too many.

United We Stand, Divided We Fall

Do you remember when there were a dozen ways to create Dependency Injection Container? Fortunately, the PSR-11 was born to solve this and moved our focus to things that matter more.

We don't want to play with colors, with fg, underscore, green, cyan (wtf is cyan?) words. Also, you know what they say:

Strings?
Break things.

We want to print the error and get back to coding.

Symfony 2.8 to the Rescue

I was super happy when the SymfonyStyle helper class came with Symfony 2.8. Simple wrapper about all mentioned above, success() method, error() method, all in API.

I think it's not an understatement to say that SymfonyStyle is state of art in this matter.

1. It's Easy to Integrate into Symfony Command

PHPStan is using it:

<?php

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;

final class SomeCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->write('Working on it!');
+         $symfonyStyle = new SymfonyStyle($input, $output);
+         $symfonyStyle->note('Working on it!');
+         $symfonyStyle->success('DONE!');
   }
}

2. Don't Make the User Think

When I was 13 years old I've accidentally read Don’t Make Me Think, amazing book about UX, programming and psychology for dummies (I'm about to read 2014-revised version). The main point of the book was the Apple, the UX, and the DX mantra - create a design that users already expect, don't teach them doing common things differently.

I recall many CLI Apps that each has different output - no colors, different font-size, cool underlines, error message is not red but success is green etc. User have to focus on the design and understand it instead of enjoying your app. WTF of non-red exception is just great!

This class offers a common way not to make use think. ECS users it, Statie uses it, PHPStan uses it, Rector uses and Steward use it.

3. SymfonyStyle as a Service

You can create SymfonyStyle in simple static construction as in point 1, but what if you need it somewhere else than in a command? Imagine you have 1200 long Command (~= Controller) and you want to extract logic to another class?

Do you have to pass the whole command there or move the SymfonyStyle manually?

Save the vendor-locking statics for value objects and enjoy the constructor injection. There are more lines than one because we need to register Input and Output as a service and autowire their interfaces.

services:
    # SymfonyStyle
    Symfony\Component\Console\Input\ArgvInput: ~
    Symfony\Component\Console\Input\InputInterface:
        alias: 'Symfony\Component\Console\Input\ArgvInput'
    Symfony\Component\Console\Output\ConsoleOutput: ~
    Symfony\Component\Console\Output\OutputInterface:
        alias: 'Symfony\Component\Console\Output\ConsoleOutput'
    Symfony\Component\Console\Style\SymfonyStyle: ~

 <?php

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

 final class SomeCommand extends Command
 {
+    public function __construct(private SymfonyStyle $symfonyStyle)
+    {
+         parent::__construct();
+    }

     protected function execute(InputInterface $input, OutputInterface $output)
     {
-         $symfonyStyle = new SymfonyStyle($input, $output);
-         $symfonyStyle->note('Working on it!');
+         $this->symfonyStyle->note('Working on it!');
-         $symfonyStyle->success('DONE!');
+         $this->symfonyStyle->success('DONE!');
    }
}

4. Show Your Style

Last little detail that makes the whole experience nice and smooth. EasyCodingStandard uses SymfonyStyle, but it needed to add 1 extra method.

final class OurStyle extends SymfonyStyle
{
    public function pink(string $message)
    {
        // ...
    }
}

One real example for all, check StewardStyle class on Github.



And that's it!

Try the simple approach today and you'll see you won't regret it:

<?php

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

final class SomeCommand extends Command
{
     protected function execute(InputInterface $input, OutputInterface $output)
     {
         $symfonyStyle = new SymfonyStyle($input, $output);
         $symfonyStyle->note('Working on it!');
         $symfonyStyle->success('DONE!');
    }
}

Happy coding!




Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!