Cleanest way to globally override a Console style

Viewed 208

tl;dr: I want to change an output formatter style across my entire Console application without having to modify every command. How can I make a single set of changes that take effect globally?

I want to globally change the error output formatter style in my Symfony 4 Console application. As per the documentation, it's easy to do so in an ad hoc fashion per command, e.g.:

public function execute(InputInterface $input, OutputInterface $output): int {
  $output->getFormatter()->setStyle('error', new OutputFormatterStyle('red'));
}

But I don't want to add needless boilerplate to all my commands--especially not with a new operator. For maintainability and testability, I prefer to override and inject my dependencies via the service container. I tried to do this by overriding the output formatter:

MyOutputFormatter.php

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class MyOutputFormatter extends OutputFormatter {

  public function __construct($decorated = FALSE, array $styles = []) {
    // I've tried it this way:
    $styles['error'] = new OutputFormatterStyle('red');

    parent::__construct($decorated, $styles);

    // I've tried it this way:
    $this->setStyle('error', new OutputFormatterStyle('red'));

    // And I've tried it this way:
    $this->getStyle('error')->setForeground('red');
    $this->getStyle('error')->setBackground();
  }

}

services.yml:

Symfony\Component\Console\Formatter\OutputFormatterInterface:
  alias: My\Console\Formatter\OutputFormatter

MyCommand.php

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

class MyCommand extends Command {

  public function execute(InputInterface $input, OutputInterface $output) {
    $output->writeln("<error>I'm an error.</error>");
  }

}

But I must be doing something wrong, because although my class definitely gets injected and interpreted, whether I try to override an existing style or create a new one, it has no effect: I expect my custom style to be used (red foreground with no background), the default style is used instead (white foreground with a red background).

Can someone correct my misunderstanding or suggest a better way? Thanks!!

1 Answers

I solved it! It turned out to be very simple: The Application class has a configureIO() method for precisely this purpose. This is all I had to do in the end:

Application.php

class Application extends \Symfony\Component\Console\Application
{
    protected function configureIO(InputInterface $input, OutputInterface $output): void
    {
        $output->getFormatter()->setStyle('error', new OutputFormatterStyle('red'));
    }
}

Note that this solution assumes that you're using your own Application class in your front script, e.g.:

bin/my-command.php

$application = new \My\Application();

$application->add(new MyCommand());
// etc.

$application->run();
Related