Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

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

Merged

Conversation

chalasr
Copy link
Member

@chalasr chalasr commented Jan 10, 2017

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:

// 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

After:
after

@javiereguiluz
Copy link
Member

javiereguiluz commented Jan 10, 2017

I don't know if it makes sense, but I expected an empty string in the third example (instead of null).

In fact, that was my original problem: there is a command that defines a default value for the "prefix" option. I want to use an empty string as the prefix, but there's no way to tell the command that I want an empty string.

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

👍 for empty string when given empty..

cmd --foo     => ""
cmd           => null
cmd --foo bar => "bar"

@chalasr
Copy link
Member Author

chalasr commented Jan 10, 2017

Yeah, problem is that the conversion of empty strings to null is a feature (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Input/ArgvInput.php#L224).
So, should we change this behavior? If we agree, I'll try to do it here.

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

Imo. the distinction between null/default and "" makes sense when compared to the command line.

@javiereguiluz
Copy link
Member

@ro0NL I think we are talking about different behaviors.

You say:

command            ==> foo = null
command --foo      ==> foo = empty string
command --foo=""   ==> foo = empty string

And this is what I say:

command            ==> foo = null
command --foo      ==> foo = null
command --foo=""   ==> foo = empty string

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

Yep.. you're right. Maybe even better 👍

However i considered it (for my own usecase 👼) as follow

  • cmd (foo is default)
  • cmd --foo="" (foo is given)
  • cmd --foo="bar" (foo is given)
  • cmd --foo (foo is null)

In the last scenario i'd like to throw if the command is running non-interactively (--foo requires a value by design), otherwise, if interactive, and --foo is passed, i'd like to ask a value.

But you're right, this is a different look at it.

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

We actually talking about the same :) --foo (null) and --foo="" ("") I wrote it down wrong :)

👍

edit: depends though.. i vote for --foo (when given) being NULL at all time, not the default value (which can be null).

@chalasr
Copy link
Member Author

chalasr commented Jan 10, 2017

i vote for --foo (when given) being NULL at all time

It's what this actually fixes:

// before
cmd          // $input->getOption('foo') gives the default value
cmd --foo    // $input->getOption('foo') gives the default value
cmd --foo="" // $input->getOption('foo') gives the default value

// after
cmd          // $input->getOption('foo') gives the default value
cmd --foo    //  $input->getOption('foo') gives NULL
cmd --foo="" // $input->getOption('foo') gives NULL (remaining problem, should give an empty string)

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

My bad :), your last after example sounds really good (with the correct empty string handling).

Maybe for the input definition we could/should make things opt-in? InputOption::VALUE_NULL or so, otherwise im 👍 for the behavior change.

Ie. --foo being NULL vs. default will be a never ending discussion i guess. One can expect both actually..

@ogizanagi
Copy link
Contributor

Ie. --foo being NULL vs. default will be a never ending discussion i guess. One can expect both actually..

IMHO it must be null. You already get the default by calling cmd without the option. Why would you add the option then if you expect the default?

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

I tend to agree null makes more sense as well.. as it's rather explicit. However, i could understand one saying "no value means the default value", as we technically did not pass a value. Other then null of course.. or is it void? ;-)

Anyway.. concerning BC.. what about making the behavior opt-in? VALUE_NULL could differ between giving null (new behavior), or default (old behavior).

@chalasr
Copy link
Member Author

chalasr commented Jan 10, 2017

what about making the behavior opt-in?

It would add too much complexity to something already quite complex imho, from a code pov as well as from a usage one: reading VALUE_NULL is quite confusing to me, since other constants VALUE_OPTIONAL and VALUE_REQUIRED are not about using or not the default value but only that the option must take a value or not.
I would say it's a minor behavior break which is acceptable and quite easy to fix (i.e. don't pass --prefix with no value if your option has a default value, no code to change).

About empty strings, I think we can't handle them while keeping a consistent behavior.
Fact is that running cmd --foo="" or cmd --foo= gives the same result when looking at $argv, i.e. array('cmd', '--foo=');. So what do we do for the later, null or empty string?
It looks like a PHP limitation (don't know about other languages) and we have to make a choice, giving always null or always an empty string. I prefer null, as the current implementation gives, fixable using a --no-prefix option.

This fixes the fact that default value is used when giving an empty value, another issue could be opened for the fact that null is returned for an empty string, but I'm afraid we can't fix without totally changing the way we get and parse the input (and I don't see how actually).
/cc @javiereguiluz @fabpot

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

I would say it's a minor behavior break which is acceptable and quite easy to fix

👍

Imo. --foo= equals --foo="". The trailing = is trivial here.

@javiereguiluz
Copy link
Member

@ro0NL I don't think it's the same: An empty string is not the same as null. To me, --foo and --foo= means that there is no value, so null. However, --foo='' is different because we are passing an empty string as the value, so there is a value and it's an empty string.

@ro0NL
Copy link
Contributor

ro0NL commented Jan 10, 2017

But quotes are optional.. --foo=bar equals --foo="bar" as well (and equals --foo bar).

@chalasr
Copy link
Member Author

chalasr commented Jan 10, 2017

However, --foo='' is different because we are passing an empty string as the value, so there is a value and it's an empty string

Sadly, regarding php it is not:

// php cli.php --foo='bar'
dump(getopt('', ['foo::'])); // ["foo" => "bar"]
dump($argv); // [0 => "cli.php", 1 => "--foo=bar"]

//  php cli.php  --foo=
dump(getopt('', ['foo::'])); // []
dump($argv);  // [0 => "cli.php", 1 => "--foo="]

//  php cli.php --foo=''
dump(getopt('', ['foo::'])); // []
dump($argv); // [0 => "cli.php", 1 => "--foo="]

But:

//  php cli.php --foo="''"
dump(getopt('', ['foo::'])); // []
dump($argv); // [0 => "cli.php", 1 => "--foo=''"]

edit: For the record, I tried some of the most used CLI packages (hoa/console, auraphp/Aura.Cli, nategood/commando and c9s/CLIFramework) and none of them behave "perfectly" regarding our need. The fact we use null for no-value and not an empty string looks debatable and could be reconsidered, both have pros and cons.

@javiereguiluz
Copy link
Member

@chalasr OK. It's clear now that this problem has no solution because of PHP. We should instead add a note in the docs explaining that you cannot pass an empty string as the value of an option. Thanks for investigating this!

@chalasr
Copy link
Member Author

chalasr commented Jan 14, 2017

@javiereguiluz As explained in #21215 (comment), there is actually 2 different issues:

  • Passing an option with no value or an empty value actually returns the default value of the option, so we can't know if the option was explicitly specified or not
  • Passing an option with only quotes (empty string) is not detectable when using = as separator

The former can be solved by returning null instead of the default value (or any other empty value, but the current implementation uses null), unlocking the ability to know that the option was specified but empty, letting up to the developer to change null to an empty value of any primitive type, or use the default value. That is what is proposed here.

The latter is either full white or full black: we have to use an empty value of a predefined primitive type (currently null) to return when the option is specified with no value or an empty string.
We could have returned an empty string instead of null (as we are forced to return the same in all cases i.e. cmd --foo, --foo=, --foo="" and --foo ""), but I do think it's not a big deal (fixed), as one would complain that he wants null when using --foo, for sure.
However, it is no more a blocking issue as soon as we have the fix made here, because we are aware of that the option has been specified empty, so we can easily convert null to the value of our choice (some CLI libraries allow to set a type on the option beforehand for handling these cases differently depending on the need, it could be a way to improve the situation in a next time, not sure it's worth it though).

@ro0NL
Copy link
Contributor

ro0NL commented Jan 15, 2017

The implicit empty string to null conversion as mentioned before, should be fixed at least imo.; looking at https://github.com/symfony/symfony/blob/v3.2.2/src/Symfony/Component/Console/Input/ArgvInput.php#L145 we already detect the difference between a value an no value depending on =. So this works counter-wise.

Including this PR the only drawback seems we cant differ between --foo and --foo "", which right now doesnt work either (gives default, after it gives null).

@chalasr
Copy link
Member Author

chalasr commented Jan 15, 2017

I looked at improving the situation for empty strings, here is what I end up with:

Code

$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 this PR
before

After
after

We get the default value only when don't passing the option explicitly.
I think we are good. What do you think?

@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch 2 times, most recently from 5fa7221 to 06b5e7f Compare January 15, 2017 11:37
@ro0NL
Copy link
Contributor

ro0NL commented Jan 15, 2017

Looks really good :) can you confirm --opt-long --opt-long2 are both null? Or vice-versa... does --opt-long "" --opt-long2 works out?

I think the latter is an edge case where you get null for --opt-long right?

@chalasr
Copy link
Member Author

chalasr commented Jan 20, 2017

Any thought here?

@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch from 5a6ca14 to 6502cb6 Compare February 28, 2017 15:24
@chalasr
Copy link
Member Author

chalasr commented Feb 28, 2017

changelog updated, ping deciders

@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch from 6502cb6 to 4c3c06e Compare February 28, 2017 16:10
@@ -1,6 +1,13 @@
CHANGELOG
=========

2.7.25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be removed, bug fixes are documented in the main CHANGELOG, which is automatically generated when I make a new release.

@fabpot
Copy link
Member

fabpot commented Feb 28, 2017

This looks like a BC break to me (and the fact that you added a note in the CHANGELOG confirms that feeling). So, I think that's not something that can be merged in 2.7, master would be ok if clearly documented.

@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch from 4c3c06e to 776653a Compare February 28, 2017 22:35
@chalasr chalasr changed the base branch from 2.7 to master February 28, 2017 22:35
@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch 2 times, most recently from d37516e to 259e8be Compare February 28, 2017 23:17
@chalasr
Copy link
Member Author

chalasr commented Feb 28, 2017

@fabpot rebased on master and documented.

@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch 2 times, most recently from af4719e to f3d678f Compare February 28, 2017 23:31
@chalasr chalasr force-pushed the console/keep-empty-value-for-options branch from f3d678f to 8086742 Compare February 28, 2017 23:37
@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

Thank you @chalasr.

@fabpot fabpot merged commit 8086742 into symfony:master Mar 1, 2017
fabpot added a commit that referenced this pull request 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
@ro0NL
Copy link
Contributor

ro0NL commented Mar 1, 2017

Thanks @chalasr. Made my day 😊

@chalasr chalasr deleted the console/keep-empty-value-for-options branch March 1, 2017 08:30
@fabpot fabpot mentioned this pull request May 1, 2017
fabpot added a commit that referenced this pull request Jun 14, 2017
… (chalasr)

This PR was merged into the 3.4 branch.

Discussion
----------

[FrameworkBundle] Deprecate useless --no-prefix option

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

It was a workaround, not needed since #21228. Let's deprecate it and remove it in 4.0.

Commits
-------

f7afa77 [FrameworkBundle] Deprecate useless --no-prefix option
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this pull request Jun 14, 2017
… (chalasr)

This PR was merged into the 3.4 branch.

Discussion
----------

[FrameworkBundle] Deprecate useless --no-prefix option

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

It was a workaround, not needed since symfony/symfony#21228. Let's deprecate it and remove it in 4.0.

Commits
-------

f7afa777d8 [FrameworkBundle] Deprecate useless --no-prefix option
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants