New in Symfony 5.3: Negatable Command Options

Symfony 5.3 is backed by JoliCode. JoliCode is a team of passionate developers and open-source lovers, with a strong expertise in PHP & Symfony technologies. They can help you build your projects using state-of-the-art practices.

In some console commands it’s common to define two related options with opposite behaviors. For example, the default options applied to all Symfony commands include the --ansi and --no-ansi options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ...
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;

class SomeCommand extends Command
{
    // ...

    protected function configure(): void
    {
        $this
            // ...
            ->addOption('ansi', null, InputOption::VALUE_NONE, 'Force ANSI output')
            ->addOption('no-ansi', null, InputOption::VALUE_NONE, 'Disable ANSI output')
        ;
    }
}

In Symfony 5.3 we’ve introduced negatable command options to simplify these commands. A single negatable option creates two options in the command, following the pattern --xxx and --no-xxx. In practice, the following is equivalent to the previous example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ...
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SomeCommand extends Command
{
    // ...

    protected function configure(): void
    {
        $this
            // ...
            ->addOption('ansi', null, InputOption::VALUE_NEGATABLE, 'Force/disable ANSI output')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // if command is run as `command-name`,           $useAnsi = null
        // if command is run as `command-name --ansi`,    $useAnsi = true
        // if command is run as `command-name --no-ansi`, $useAnsi = false
        $useAnsi = $input->getOption('ansi');

        // ...
    }
}

Negatable options are only available for options that don’t allow passing any value to them (their previous type should be InputOption::VALUE_NONE).

Help the Symfony project!

As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.

Comments

So gooood !!
In the above example, without passing any --ansi or --no-ansi, will $useAnsi be true or false as default?
Awesome! 🎉
@Martin if you don't pass any of the two options, the value is FALSE.
@martin - I believe you can do

->addOption('ansi', null, InputOption::VALUE_NEGATABLE, 'Force/disable ANSI output' , 'DEFAULT HERE')

but otherwise, false as per @javier
@Javier I don't see the purpose of this if default value would be FALSE - it'd do the same work as InputOption::VALUE_NONE (not defined option returns FALSE, why to use --no-value to get the same result?) ... I think the most useful behaviour would be returning NULL by default to behave like nullable/3-state boolean - YES / NO / DON'T CARE ...
It's nice. Thank you :)
I agree with @Jan Egert
bin/console would then be default as --no-ansi - and therefor all the default commands would run without any color coding.

So get color coding, you need to run bin/console --ansi then?
I see that a few posters already mentioned the desire for tri-state logic for this, as well as the possible confusion about usage of the feature (is it tri-state or 2-state by default ?).

So, for once, not a great thumbs up from my part. It seems a confusing API that needs documentation to clarify its default behaviour...
I'd really appreciate 3-state boolean behavior with possibility to mark option as REQUIRED to be able to force user to choose between --option and --no-option and possibility to define default value if optional ... it'd make perfect sense for me. But as mentioned above, it was said default value is FALSE = by default it behaves the same as VALUE_NONE.

3-state boolean would be very useful - I often use this pattern for example for data filtering - aka to get list of activated/deactivated/all users it could be --activated, --no-activated or not defined to get all.

Also - it'd be nice to support also keyword "not" just for better readability (--not-activated instead of --no-activated) but I understand it could be confusing in documentation.
Thanks to your feedbacks, "3 states" negatables are now implemented:
https://github.com/symfony/symfony/pull/40986
Thank you all for your feedback. As Nicolas said, we've changed this a bit, so I've updated the example of this blog post to reflect the latest changes. Thanks!
:-x

Comments are closed.

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