Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Documenting how to keep option value BC - see #14377 #14825

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
merged 1 commit into from
Jun 10, 2015

Conversation

weaverryan
Copy link
Member

Q A
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #14377 (kinda)
License MIT
Doc PR symfony/symfony-docs#5179

Hi guys!

I'm still making sense of the form changes, but it seems that this before and after isn't totally honest - your option values will change, unless your add this extra option.

@webmozart look correct to you?

Thanks!

@jakzal jakzal added the Form label Jun 2, 2015
@xabbuh
Copy link
Member

xabbuh commented Jun 2, 2015

Is that intended? To me, if that is the solution, the choices_as_value option doesn't have the right name (it promises something else than it does).

@weaverryan
Copy link
Member Author

@xabbuh Good point - there is a confusion between "value" as in "this is the value that will be returned by the form" and "value" as in "this is the value attribute of the option". These are not the same thing, so it can be confusing. But yes, I believe this is all intended.

@apfelbox
Copy link
Contributor

apfelbox commented Jun 2, 2015

@xabbuh if I understood the initial implementation correctly choices_as_values simply means, that the choice values (as in your model data) is in the values of the 'choices' => [...] array instead of the keys (like it was before).

The reason behind this change was to easily allow values of types other than int or string as choice values. This was apparently implemented by ignoring the model value when creating the html value="", which is required for some cases, but is not for other cases.

This should be documented, but it is no BC issue as the option previously just didn't exist.

@xabbuh
Copy link
Member

xabbuh commented Jun 2, 2015

@apfelbox If I understand you correctly, it is nothing more than a trigger for the array_flip() call. Is that what you mean?

@apfelbox
Copy link
Contributor

apfelbox commented Jun 2, 2015

@xabbuh something in this direction. It is a way to provide both a) a comfortable and minimally verbose way of configuring the choices option (no object, nothing to implement just a plain array) and b) supporting model values in this compact form that don't work as PHP array keys (floats, objects, etc..).

I guess historically the array was defined this way, because the assumption was that the model values will be unique per options. Now the realization came, that the label probably is unique per option, too (otherwise the user can't differentiate different values). And by using this direction one can use any arbitrary type as model data – as the label is required to be a string it is optimal for usage in the array keys.

But as arbitrary model data can't be embedded into the value="" field, the simplification is made, that the values are just numbered (although the model data could be of a type, that can be embedded into HTML – but this is not guaranteed).

So therefore... it is more of a convenience method of expanding the usage of the compact choice array definition.

(... but as I keep thinking about that, I just realized that there is another possible issue with this implementation (maybe someone could verify that): as the choices are just numbered now, the order of the choices in the array suddenly becomes important. Although it would be weird for these options to have an arbitrary order, this currently would produce wrong results in the current implementation ("wrong" as in you select option "a", but after page refresh you selected option "b"). Probably not frequent enough, but I just wanted to mention it.)

@davedevelopment
Copy link
Contributor

Now the realization came, that the label probably is unique per option, too (otherwise the user can't differentiate different values

I have a situation where I have duplicate labels, but they are in different groups. I'm not 100% sure if this use case is supported, as I'm still trying to make sense of the changes.

@weaverryan
Copy link
Member Author

@davedevelopment I've thought about this - I believe the answer is that you should make your array keys unique, but then use choice_label (either with a callback or a property path) as described in #14050.

I'm with you - I understand why the change was made, but I'm still making sense of it too :).

@davedevelopment
Copy link
Contributor

@weaverryan you legend, just fixed something in 3 minutes that I've been working on for about 3 hours :)

@davedevelopment
Copy link
Contributor

And the fix is in production, thanks again @weaverryan, I owe you big time

@weaverryan
Copy link
Member Author

@davedevelopment too nice :)

@weaverryan
Copy link
Member Author

@webmozart I'm pretty sure this is an easy merge into the UPGRADE log - we just need you to confirm - thx :)

@peterrehm
Copy link
Contributor

@weaverryan Why is the change even required? It feels kind of weird.

@stof
Copy link
Member

stof commented Jun 10, 2015

👍

@fabpot
Copy link
Member

fabpot commented Jun 10, 2015

Thank you @weaverryan.

@fabpot fabpot merged commit deb9db8 into symfony:2.7 Jun 10, 2015
fabpot added a commit that referenced this pull request Jun 10, 2015
…averryan)

This PR was merged into the 2.7 branch.

Discussion
----------

Documenting how to keep option value BC - see #14377

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #14377 (kinda)
| License       | MIT
| Doc PR        | symfony/symfony-docs#5179

Hi guys!

I'm still making sense of the form changes, but it seems that this before and after isn't totally honest - your option values will change, unless your add this extra option.

@webmozart look correct to you?

Thanks!

Commits
-------

deb9db8 Documenting how to keep option value BC - see #14377
@Tobion
Copy link
Contributor

Tobion commented Jun 10, 2015

This change makes no sense to me. Why not use the choice as option value by default as before? That looks like the better default. With this change, developers have to ensure the options are always sorted by ID basically and not by changing values. Otherwise you will run into TOCTTOU problems as decribed in #14377 (comment)

@soullivaneuh
Copy link
Contributor

In addition it should be better to advice user to keep this option to false instead or writing 3 additional lines for a closure getting a choice and... returning this choice.

BTW, what does that mean? This is really not comprehensive IMO.

'choices_as_values' => false

@stof
Copy link
Member

stof commented Jun 11, 2015

@soullivaneuh using choices as values is planned to become the default in Symfony 3.0

@soullivaneuh
Copy link
Contributor

@stof I know. But what is the advantage of adding a closure like this to get this working properly with some JS forms? Don't we have proper way?

@jvasseur
Copy link
Contributor

Another problem of using this without the form_value option is if you generate your options dynamically, the order could change between displaying the form and submitting it leading hard to find bugs.

@apfelbox
Copy link
Contributor

@jvasseur this is probably the main issue. As the order doesn't have to be guaranteed, this component becomes unreliable.

@webmozart
Copy link
Contributor

@soullivaneuh The main advantage is that you can now use any kind of data as choice. Before, you were limited to strings and integers, because any other values are not valid array keys.

The downside is that we cannot guarantee anymore that choices can be cast to strings that uniquely identify the choice. For array keys, we could do that.

As a consequence you now need to implement your own "choice_value" closure where you apply your knowledge of your domain to return unique strings for each choice - even if that's the choice itself. But Symfony can't guess that magically for you.

@soullivaneuh
Copy link
Contributor

@webmozart What I'm not understanding it's that tricky closure just returning the choice given on anonymous function argument, without any modification.

I found that is writing code for nothing IMO. Why not enable this by default?

@webmozart
Copy link
Contributor

@soullivaneuh Because that would be wrong in some use cases, for example:

$builder->add('field', 'choice', array(
    'choices' => array(null, true, false),
    'choices_as_values' => true,
));

If we create the choice values by casting choices to strings, we'll receive the values "", "1" and "" - which do not uniquely identify the choices. Hence, by default, incrementing integers are generated.

Note that the "entity" type will try to use the entity ID as value if possible (i.e. if it's not a composite ID).

@soullivaneuh
Copy link
Contributor

Thanks for this explanation. I see the problem now.

But I'm still think that set 'choices_as_values' => false, would be a simpler choice in this case. :-)

@webmozart
Copy link
Contributor

@soullivaneuh Yes you are right :) At the moment, we have "choices_as_values" which can take two values:

  • false: works as long as choices are either integers or strings
  • true: much more flexible, but also a bit more work as you showed

The question is how we'll deal with this option in the future? Again, we have two possibilites:

  1. either we keep the "choices_as_values" option and document its effects and when to use true and when false
  2. or we remove it, default to true and document how to use "choice_value"

I think that the second solution is much easier to understand, even if it means a little more (trivial) work.

@soullivaneuh
Copy link
Contributor

For the future, I would prefer solution 1 by advising to set this option to false for this special case.

But I would say also:

aoz8kgx8pzknypz7z38n

;-)

@webmozart
Copy link
Contributor

I know :) But keeping both possibilities also means maintaining both of them, documenting both of them and upgrading both of them when introducing new functionality. This makes it much harder and more time consuming to change the code base.

I prefer removing non-essential features that currently only slow development down so that we can add essential features more easily in the future.

@soullivaneuh
Copy link
Contributor

But keeping both possibilities also means maintaining both of them, documenting both of them and upgrading both of them when introducing new functionality.

Well, 'choices_as_values' => false will be available on 3.0, isn't it?

documenting both

I'm speaking about this trick notes of this PR. ;-)

@webmozart
Copy link
Contributor

will be available

No: #14951

@xabbuh
Copy link
Member

xabbuh commented Jun 12, 2015

@webmozart Maybe we should just provide an option that sets choice_label to function ($choice) { return $choice; }. Shouldn't be that hard to maintain in the component, but saves developers to write cumbersome and boring code.

@webmozart
Copy link
Contributor

@xabbuh Automate a one-liner by adding another one-liner?

@xabbuh
Copy link
Member

xabbuh commented Jun 12, 2015

@webmozart I have the feeling that this could be more intuitive and more easy to remember.

@peterrehm
Copy link
Contributor

How about having a different form type for those array choices which inherits all the functionality and provides the easy default settings? Should improve the UX without much additional work to maintain.

@webmozart
Copy link
Contributor

@xabbuh I think that the main problem at the moment is that we're changing things. People are naturally reluctant to change, since it means that they need to learn something new even if they had to learn much more than newcomers will have to learn after the change.

The only sane automatism I can imagine is to traverse the options and see if they contain scalars only that can be cast to duplicate-free strings. But that means worse performance just to automate away a one-liner.

In any case, IMO the way to go is to do this, wait until people get used to the new API and then try to improve/automate things if necessary. That's what we did for 2.1 and quite successfully so.

@soullivaneuh
Copy link
Contributor

I think that the main problem at the moment is that we're changing things. People are naturally reluctant to change, since it means that they need to learn something new even if they had to learn much more than newcomers will have to learn after the change.

Well, in my personal case, I'm not "reluctant to change" at all.

I'm reluctant when the change introduce the usage obligation of an obscure closure just to fix a JavaScript trick.

This is why I'm discussing here. :-)

@stof
Copy link
Member

stof commented Jun 12, 2015

And btw, the usage of incremented integers already exists in Symfony 2.3+ in other cases:

  • entity choice list with a composite id
  • passing a ChoiceList instead directly rather than a SimpleChoiceList (for instance because you need boolean choices or float choices)
  • passing an ObjectChoiceList which does not use the valuePath setting

@webmozart
Copy link
Contributor

@stof Exactly. So what's new is that the user of the choice field is now explicitly responsible for choosing a unique string representation for their choices.

If the closure is the big hassle, we can also support some magic like:

$builder->add('field', 'choice', array(
    'choices' => array(1, 42, 128),
    'choice_value' => '__choice__',
));

But I don't think that this is clearer than:

$builder->add('field', 'choice', array(
    'choices' => array(1, 42, 128),
    'choice_value' => function ($choice) { return $choice; },
));

@soullivaneuh
Copy link
Contributor

@webmozart I'm divided about your proposition. :|

'__choice__' is indeed quicker to write, but also more magic and obscure IMO.

@Tobion
Copy link
Contributor

Tobion commented Jun 13, 2015

Let's say we have string/integer values as choices. Why not use that as value by default then?
Using incrementing integers only makes sense to me for non-scalar values, where you cannot be sure about the unique string representation.

So for common cases like 'choices' => array(1, 42, 128), just use these choices as value as well.
And the argument about 'choices' => array(null, true, false), can also be solves by just fixing the string represenation for false -> '0' and null -> ''.

Even if you have the same scalar twice in the options it makes no difference since when setting the value on the model there is no difference between the two.
So a 'choices' => array(1, 1, 1), would not be distinguishable when submitting one or the other anyway.

And what we could also do is just check if the choices are unique stringrepresented scalars and only use incrementing integers if not.

@webmozart
Copy link
Contributor

And what we could also do is just check if the choices are unique stringrepresented scalars and only use incrementing integers if not.

Yes we could do that. That's what I was trying to say with:

traverse the options and see if they contain scalars only that can be cast to duplicate-free strings

However, we have to check how big the performance penalty is.

@weaverryan weaverryan deleted the upgrade-form-choices_as_values branch June 19, 2015 16:48
@ghost
Copy link

ghost commented Jul 23, 2015

Hi, I'm not very experienced with symfony and I landed here when I wanted to file a bug report. I have forms 2.7.1 and filled 'choices' with an array with keys and values for values and labels (as explained here). My special case is that for two different values I have the same label. When I pass to the form the value of the first option as selected value it decides the value is null when I pass the second value it decides the value is the correct one. I think this is a bug and the reason is that values are for some reason internally reverse mapped to labels and the first pair is therefore overwritten (in ChoiceList::getChoicesForValues you get the inconsistent behavior), which for me is not expected behavior with the simple use of the choices array (the program should not care about the labels besides displaying them).
I understand there is a need to have values with special characters, but this should not affect the simple behavior of the choices option. Further it would be nice if the documentation would be updated, because at the moment with different behavior and deprecated classes it's not clear at all what needs to be done when.
To solve the problem for values that contain special characters I would simply allow one of these 2 options:

  • in the choices array have the option to specify for each option array('value'=>$value,'label'=>$label)
  • if a choices_labels array is specified take the values of the choices array as values (disregard keys) ant the values of the choices_lables array as labels (also disregarding keys, just keep the order).
    Both solutions would avoid to use labels for reverse mapping (which I think is a big no go).

nicolas-grekas added a commit that referenced this pull request Dec 18, 2015
This PR was merged into the 2.8 branch.

Discussion
----------

Upgrade information for the choice_value option

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

Relates to #14825 and #14377. The behaviour was changed with #16681 so a not in the upgrade information makes sense to me.

Commits
-------

28675c9 Reflected the change of the choice_value option in the Upgrade information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.