8000 [Console] Option with optional argument always returning null whether specified or not · Issue #11572 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Console] Option with optional argument always returning null whether specified or not #11572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
EdwardIII opened this issue Aug 5, 2014 · 14 comments

Comments

@EdwardIII
Copy link

From the docs (http://symfony.com/doc/current/components/console/introduction.html#using-command-options):

It is also possible to make an option optionally accept a value (so that --yell
or --yell=loud work). Options can also be configured to accept an array of values.

In practice it doesn't seem to work this way. When creating a Command like this:

    <?php
                ->addOption(
                    'createfile',
                    null,
                    InputOption::VALUE_OPTIONAL,
                    'Do not delete the file, specify argument to choose an alternative filename'
                )


    ...

                $createfile = $input->getOption('createfile');
                var_dump($createfile); // returns NULL

And calling it like this:

php app/console my:command --createfile

The value of $createfile is null, even though the switch is present.

I can't find any way to identify that the command is present unless it has a value.
i.e. php app/console my:command --createfile='/tmp/somefile.txt' does what you'd expect and returns the string "/tmp/somefile.txt".

I can see no other way of identifying whether this option is present?

There seems to be some code supporting the idea that you get a different return value if the option is provided but with no optional value present:

https://github.com/symfony/symfony/blob/2.5/src/Symfony/Component/Console/Input/ArgvInput.php#L234

This looks like it should provide you with an empty string if this parameter is provided but has no value, however in my tests count($this->parsed) was 0, which meant that this inner block could never get executed.

I checked out the unit tests for ArgvInput but couldn't find a test that seemed to be based on this particular use case:

https://github.com/symfony/symfony/blob/2.5/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php

Symfony version 2.5.2 - app/dev/debug

@EdwardIII EdwardIII changed the title Option with optional argument always returning null whether specified or not [Console] Option with optional argument always returning null whether specified or not Aug 7, 2014
@xabbuh
Copy link
Member
xabbuh commented Aug 7, 2014

You can pass the default value as the fifth argument (nullbeing the default).

@EdwardIII
Copy link
Author

I did give that a try, but then the default always seems to be present, even if the switch isn't provided.

To illustrate:

    <?php
                ->addOption(
                    'createfile',
                    null,
                    InputOption::VALUE_OPTIONAL,
                    'Do not delete the file, specify argument to choose an alternative filename',
                    true // so we can check $retval === true to differentiate between it being present with no value, or just not present
                )
php app/console my:command --createfile # results in a getOption retval of true
php app/console my:command # also results in a getOption retval of true

So I gave that a spin but still couldn't use it to tell the difference between the option being present with no value or not being present at all.

@stof
Copy link
Member
stof commented Aug 8, 2014

there is indeed currently no different between the option being not passed, being passed with no value or being passed with the same value than the default (in case the default is a string as only strings can be passed)
Note that the same is true for values with required value (except there is 2 cases only rather than 3)

Changing getOption() is not possible as it would be a BC break (and would make many valid use cases harder to implement).
However, we could add a new method to check whether the option is present or no (still difficult in term of BC though as we cannot change the InputInterface itself for BC reasons)

@EdwardIII
Copy link
Author

@stof Thanks for the feedback!

Does that make this a documentation bug, or am I misunderstanding?

http://symfony.com/doc/current/components/console/introduction.html#using-command-options

"It is also possible to make an option optionally accept a value (so that --yell or --yell=loud work). Options can also be configured to accept an array of values."

It seems that this feature (optional args for switches) actually serves no purpose if you can't differentiate? Sorry if I'm missing the point!

@xabbuh
Copy link
Member
xabbuh commented Aug 8, 2014

@EdwardIII I agree, it doesn't seem to be possible as intended by the documentation. If we can't find a backward compatible way to fix this, we should at least be more clear in the documentation. Do you like to open an issue over there?

< 8000 /div>

@ocke
Copy link
ocke commented Sep 8, 2014

Hey Guys,

I ran into this exact same problem.

So I forked symfony's/console, and tried to build a solution. Would you guys like to have a look at my tiny try of making console more awesome? It is a proof of concept, I'm not saying this should be made into a PR yet. I just want to share my thoughts and possible implementation of how we could get this to work. Yes the current name of the constant is a silly pun...

A couple of notes:

  • I assume you guys agree that it's not a good idea to break current functionality. Therefor VALUE_OPTIONAL will stay how it is and I added a new constant: VALUE_OPTIONULL.
  • VALUE_OPTIONULL will result in:
    • Not setting the longoption will make the default set to false, just like a standard longoption.
    • Setting the longoption without a value will set it to it's default
    • Setting the longoption with a value will set it to that value.

Let's say --dev

php tool.php
array(1) {
  'dev' =>
  bool(false)
}
php tool.php --dev
array(1) {
  'dev' =>
  'the default value'
}
php tool.php --dev=test
array(1) {
  'dev' =>
  'test'
}
  • I like lists so i summerize
  • All the tests still work!
  • We need to come up with a better name for the config option
  • I'll need to write new test for the new config option (VALUE_OPTIONULL)

And finally the code:
https://github.com/ocke/symfony/compare/optional_with_false_2

@stof
Copy link
Member
stof commented Sep 8, 2014

@ocke Please open a PR. It is much easier to review the code

@ocke
Copy link
ocke commented Sep 8, 2014

@stof ok done :-)

#11883

@c33s
Copy link
c33s commented Mar 17, 2016

workaround

command definition:

$this
    ->setName('demo:greet')
    ->setDescription('Greet someone')
    ->addOption('yell', null, InputOption::VALUE_OPTIONAL, 'If set, the task will yell in uppercase letters')
;

in the execute method

$result1 = $input->hasParameterOption('--yell');
$result2 = $input->getOption('yell');

console

php ./bin/console demo:greet
// $result1 == false
// $result2 == null

php ./bin/console demo:greet --yell
// $result1 == true
// $result2 == null

php ./bin/console demo:greet --yell=foo
// $result1 == true
// $result2 == foo

code example:

//only set build if option was set. if set with no value, set it to empty (default value)
if ($input->hasParameterOption('--build')) {
    $build = $input->getOption('build');
    if (!is_array($build)) {
        $build = explode('.', $build);
    }
    $builder->setBuild($build);
}

@andkirby
Copy link
andkirby commented Jul 29, 2016

Yeah, this is really overhead to check such options...

    /**
     * Check if empty password option passed
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return bool
     */
    protected function hasEmptyPasswordOption(InputInterface $input)
    {
        return null === $input->getOption('password') 
            && ($input->hasParameterOption('-p') || $input->hasParameterOption('--password'));
    }

Is there any simpler way?..

@dguhl
Copy link
dguhl commented Aug 22, 2016

@andkirby You might want to use an array for the call.

    /**
     * Check if empty password option passed
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return bool
     */
    protected function hasEmptyPasswordOption(InputInterface $input)
    {
        return null === $input->getOption('password') 
            && ($input->hasParameterOption(['-p', '--password']));
    }

(Edit)
I would completely omit the second condition in this method.

@andkirby
Copy link

@dguh 8000 l, yeah, I didn't know the method hasParameterOption may get an array.
Thanks.

@dotmpe
Copy link
dotmpe commented Nov 10, 2016

hasParameterOption is the key that makes this usable, for years I believed the InputOption::VALUE_OPTIONAL part of console was broken, in favour of "BC".

It is not mentioned on https://symfony.com/doc/current/console/input.html, while "there is no way you can distinguish when the option was used without a value" is.

fabpot added a commit that referenced this issue Mar 1, 2017
…empty) should remain empty (chalasr)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[Console] Explicitly passed options without value (or empty) should remain empty

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #21215 #11572 #12773
| License       | MIT
| Doc PR        | n/a (maybe look at updating the existing one)

This conserves empty values for options instead of returning their default values.

Code:
```php
// cli.php
$application = new Application();
$application
    ->register('echo')
    ->addOption('prefix', null, InputOption::VALUE_OPTIONAL, null, 'my-default')
    ->addArgument('value', InputArgument::REQUIRED)
    ->setCode(function ($input, $output) {
        var_dump($input->getOption('prefix'));
    });
$application->run();
```

Before:
![before](http://image.prntscr.com/image/157d9c6c054240da8b0dce54c9ce24d6.png)

After:
![after](http://image.prntscr.com/image/4aeded77f8084d3c985687fc8cc7b54e.png)

Commits
-------

8086742 [Console] Explicitly passed options without value (or empty) should remain empty
@fabpot fabpot closed this as completed Mar 1, 2017
keradus added a commit to PHP-CS-Fixer/PHP-CS-Fixer that referenced this issue Apr 21, 2017
This PR was merged into the 2.2 branch.

Discussion
----------

FixCommand - fix diff optional value handling

regression fix for #2554:
```php
new InputOption('diff', '', InputOption::VALUE_OPTIONAL, 'Also produce diff for each file.', 'sbd'),
```
that means:
- 'diff' option may or may not be passed
- when it's passed, value for it is optional
- 'sbd' is default value of `diff` option regardless it is passed or not (!!!)

I guess assumption during creating 2254 was that `sbd` would be only a value for `diff` if one would pass `--diff` (without value). Apparently, it is a value even if one would not pass that option at all, leading to generate diff even without using `--diff` option.

ref symfony/symfony#11572

Commits
-------

5546893 FixCommand - fix diff optional value handling
stevenrombauts referenced this issue in joomlatools/joomlatools-console Dec 12, 2017
The InputOption::VALUE_OPTIONAL option mode will always return
null if you don't set a default value, but returning any other value
makes it impossible to correctly determine whether the flag was
set or not.

Solution is to rely on $input->hasParameterOption() instead.

See: https://github.com/symfony/symfony/issues/11572\#issuecomment-197929086
javiereguiluz added a commit to symfony/symfony-docs that referenced this issue May 27, 2018
…roblem (Jean85, javiereguiluz)

This PR was submitted for the 2.7 branch but it was merged into the 2.8 branch instead (closes #9560).

Discussion
----------

Add 2 solutions for the 'option with optional argument' problem

While working on facile-it/paraunit#121, I discovered a tricky case with the Console component: using an option with an optional argument seemed impossible! The doc said:

> There is nothing forbidding you to create a command with an option that optionally accepts a value. However, there is no way you can distinguish when the option was used without a value (command --language) or when it wasn't used at all (command). In both cases, the value retrieved for the option will be null.

This is NOT TRUE. I've found two possible solutions to this issue (one myself, one in symfony/symfony#11572 (comment)) and this PR introduces them in the docs. I've also moved around the two tips/cautions which were at the end of the article, because with my reword it seemed nicer to me.

Commits
-------

d3f254d Minor simplifications
1aad365 Minor reword
99f9e3f Simplify the explanation leaving just one solution
a05b002 Add 2 solutions for the 'option with optional argument' problem
javiereguiluz added a commit to symfony/symfony-docs that referenced this issue May 27, 2018
…roblem (Jean85, javiereguiluz)

This PR was submitted for the 2.7 branch but it was merged into the 2.8 branch instead (closes #9560).

Discussion
----------

Add 2 solutions for the 'option with optional argument' problem

While working on facile-it/paraunit#121, I discovered a tricky case with the Console component: using an option with an optional argument seemed impossible! The doc said:

> There is nothing forbidding you to create a command with an option that optionally accepts a value. However, there is no way you can distinguish when the option was used without a value (command --language) or when it wasn't used at all (command). In both cases, the value retrieved for the option will be null.

This is NOT TRUE. I've found two possible solutions to this issue (one myself, one in symfony/symfony#11572 (comment)) and this PR introduces them in the docs. I've also moved around the two tips/cautions which were at the end of the article, because with my reword it seemed nicer to me.

Commits
-------

7be002b Add 2 solutions for the 'option with optional argument' problem
@algorhythm
Copy link

I've written an more resuable function that can answer the question for any option

public function wasOptionUsed(string $optionName): bool
{
    // raises an error if option doesn't exists
    $this->input->getOption($optionName);

    $option = $this->getDefinition()->getOption($optionName);
    $name = $option->getName();
    $shortcut = $option->getShortcut();

    return $this->input->hasParameterOption([
        '--' . $name,
        '-' . $shortcut,
    ]);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

0