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
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:
Customizing the Progress Bar Format
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
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
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
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();