The Console component has been a major focus of Symfony 8.1, and we've already covered a lot of new features in previous articles: improved console input, console argument resolvers, method-based commands, and HTTP-less Symfony applications. This article showcases more console improvements related to styling and testing.

Reporting Progress to the Terminal Taskbar

Can Vural
Contributed by Can Vural in #62112

Modern terminals can display progress information in the window title or taskbar using the OSC 9;4 escape sequence. In Symfony 8.1, progress bars emit this sequence automatically. As soon as a bar starts on a decorated output, the terminal reflects its progress, and finish() or clear() reset it.

Terminals that don't understand the sequence simply ignore it, so this feature works everywhere and requires no configuration. When several progress bars run at the same time, the indicator falls back to an indeterminate state to avoid flickering between unrelated percentages.

If your command prompts the user while a progress bar is running, you can pause the indicator with the static pauseAll() and resumeAll() methods:

1
2
3
4
5
use Symfony\Component\Console\Helper\ProgressBar;

ProgressBar::pauseAll();
// ... ask the user something ...
ProgressBar::resumeAll();

This is how it looks in practice:

Symfony 8 1 Console Progress

Customizing the Progress Bar Format

Guillaume Van Der Putten
Contributed by Guillaume Van Der Putten in #63443

The SymfonyStyle helper lets you display progress bars with a single method call, but until now you couldn't change their format without creating the bar first and then calling setFormat() on it. Symfony 8.1 adds an optional $format argument to createProgressBar(), progressStart(), and progressIterate(), allowing you to set a custom format in a single step:

1
2
3
4
5
6
7
8
9
10
// the format is the last argument, after the optional number of steps
foreach ($io->progressIterate($items, null, ' %current%/%max% [%bar%] %memory:6s%') as $item) {
    // ...
}

// the same argument is available when starting a bar manually...
$io->progressStart(100, ' %current%/%max% [%bar%] %memory:6s%');

// ... or when creating a standalone progress bar
$progressBar = $io->createProgressBar(100, ' %current%/%max% [%bar%] %memory:6s%');

A Result-Based Testing API for Commands

Théo Fidry
Contributed by Théo Fidry in #61494

Testing a command that writes to both standard output and standard error previously required the capture_stderr_separately option and two separate calls, which lost the original output ordering. Symfony 8.1 introduces a result-based testing API: the new CommandTester::run() method returns an ExecutionResult object that exposes all three views at once:

1
2
3
4
5
6
7
8
use Symfony\Component\Console\Tester\CommandTester;

$result = (new CommandTester($command))->run(['username' => 'Wouter']);

$result->getDisplay();      // stdout and stderr, interleaved
$result->getOutput();       // stdout only
$result->getErrorOutput();  // stderr only
$result->statusCode;        // the command exit code

The run() method also accepts interactive answers directly, so you no longer need a separate setInputs() call. Combined with the new ConsoleAssertionsTrait, tests become more expressive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\ConsoleAssertionsTrait;

class CreateUserCommandTest extends TestCase
{
    use ConsoleAssertionsTrait;

    public function testExecute(): void
    {
        $result = (new CommandTester($command))->run(
            ['username' => '...'],
            interactiveInputs: ['yes'],
        );

        $this->assertIsSuccessful($result);
        $this->assertResultEquals($result, expectedOutput: 'User "..." was created.');
    }
}

A Shortcut to Run Commands in Tests

Kostiantyn Miakshyn
Contributed by Kostiantyn Miakshyn in #63095

Functional tests for commands typically involve a fair amount of boilerplate: booting the kernel, creating the application, finding the command, and wrapping it in a CommandTester. Symfony 8.1 adds the runCommand() shortcut to KernelTestCase, which handles all of this for you and returns a ready-to-use tester:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class CreateUserCommandTest extends KernelTestCase
{
    public function testExecute(): void
    {
        $commandTester = static::runCommand('app:create-user', [
            'username' => '...',
        ]);

        $commandTester->assertCommandIsSuccessful();
    }
}

New Command Status Assertions

Damir Mitrovic
Contributed by Damir Mitrovic in #63130

Until now, the console testing traits only provided assertCommandIsSuccessful(). Symfony 8.1 completes the set with two new assertions that explicitly check the other command exit codes:

1
2
3
4
5
// asserts the command returned Command::FAILURE
$commandTester->assertCommandFailed();

// asserts the command returned Command::INVALID
$commandTester->assertCommandIsInvalid();
Published in #Living on the edge