In this second of a three-part series, we introduce four additional new features added by Symfony 3.2 to the Console component to improve its DX (developer experience).

Read the part 1 and part 3 of this series of articles explaining the new features of the Console component in Symfony 3.2.

Introduced a new Terminal class

Tomáš Votruba Fabien Potencier
Contributed by Tomáš Votruba and Fabien Potencier in #19012

Console's Application class defines several methods to get the dimensions (height and width) of the terminal window:

1
2
3
4
5
6
use Symfony\Component\Console\Application;

$application = new Application();
$dimensions = $application->getTerminalDimensions(); // [$width, $height]
$height = $application->getTerminalHeight();
$width = $application->getTerminalWidth();

Technically, getting this information for all kinds of terminals and operating systems is a complex, convoluted, slow and error prone process. In Symfony 3.2 we decided to move all this logic into a new Terminal class:

1
2
3
4
use Symfony\Component\Console\Terminal;

$height = (new Terminal())->getHeight();
$width = (new Terminal())->getWidth();

In addition, we improved the logic to get/set the terminal dimensions to prioritize the use of environment variables. If the COLUMNS and LINES environment variables are defined, Terminal uses their values to get the dimensions. When setting the terminal dimensions, Terminal creates or updates the values of those variables.

This new Terminal class will be used in the future to get/set more information about the terminal besides its dimensions. For now, these changes have allowed us to fix some edge cases in the progress bar helper when the terminal window was small.

Introduced a new StreamableInputInterface

Robin Chalas
Contributed by Robin Chalas in #18999

In Symfony 2.8 we introduced a new style guide for console commands that simplifies creating consistent-looking commands. However, these commands were hard to test, specially when using the ask() helper to ask for user's input.

In Symfony 3.2 we've introduced a new StreamableInputInterface and made the abstract Symfony\Component\Console\Input\Input implement it. This change allows to centralize the management of the input stream in a single class and makes the QuestionHelper related code easier to test.

Added a hasErrored() method in ConsoleLogger

Nicolas Grekas
Contributed by Nicolas Grekas in #19090

In Symfony 3.2, the ConsoleLogger class includes a hasErrored() method that returns true as soon as one message of ERROR level has been logged. This way you don't have to add any custom logic to decide whether your command should return an error exit code (exit(1)) or not.

Added a "Lockable" trait

Geoffrey Brier
Contributed by Geoffrey Brier in #18471

In Symfony 2.6 we introduced a lock handler to provide a simple abstraction to lock anything by means of a file lock. This lock handler is mainly used to avoid concurrency issues preventing multiple executions of the same command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Component\Filesystem\LockHandler;

class UpdateContentsCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $lock = new LockHandler('update:contents');
        if (!$lock->lock()) {
            // manage lock errors
        }

        // ...
    }
}

In Symfony 3.2 we made the lock handler a bit easier to use thanks to the new LockableTrait. This trait provides a lock() method that creates a non- blocking lock named after the current command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Console\Command\LockableTrait;

class UpdateContentsCommand extends Command
{
    use LockableTrait;

    protected function execute(InputInterface $input, OutputInterface $output)
    {
         if (!$this->lock()) {
             // manage lock errors
         }

        // ...
    }
}

You can also create locks with custom names and even blocking locks that wait until any existing lock is released:

1
2
3
4
if (!$this->lock('custom_lock_name')) { ... }

// the second boolean argument tells whether the lock is blocking or not
if (!$this->lock('custom_lock_name', true)) { ... }
Published in #Living on the edge