New in Symfony 3.2: Console Improvements (Part 2)

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

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

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

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

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)) { ... }

Comments

Wow, thanks for mentioning!

I'd like to point out, that first improvement mainly fixed this issue:
https://github.com/symfony/symfony/issues/13019#issuecomment-67627942

No more ugly symfony demo app installation. Happened to me week ago on lecture... For the last time now! :)

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.