Skip to content

[Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily #21267

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
wants to merge 7 commits into from

Conversation

issei-m
Copy link
Contributor

@issei-m issei-m commented Jan 13, 2017

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

Fixed ChoiceType to protect against some problem caused by treating of array.

Let's say we have the choice-form like:

$form = $factory->create(ChoiceType, null, [
    'choices' => [
        'A',
        'B',
        'C',
    ],
    'expanded' => true,
    'multiple' => true,
]);

Then, submit data like this:

$form->submit([
    [], // unnecessality nested
]);

(Yes, I agree in most cases these situation doesn't happen, but can be)

Then, we get array_flip(): Can only flip STRING and INTEGER values! error at here.
Even if form is not multiple, annoying Array to string conversion error occurs in here (via ChoicesToValuesTransformer).
(As far as I know, non-multiple and non-expanded form has no problem, thanks to ChoiceToValueTransformer)

To resolve these problems, I just added a simple-validation listener to choice type.

@nicolas-grekas nicolas-grekas added this to the 2.7 milestone Jan 16, 2017
Copy link
Contributor

@HeahDude HeahDude left a comment

Choose a reason for hiding this comment

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

Hi @issei-m, sorry I took some time to answer here, although your concern is legit this should never happen in an html context but using forms with json API might break indeed.

Since 2.x is still, maintained for years, maybe we should merge your patch, but please note that I'm working on solving this issue more globally in master and will propose a PR soon, to avoid patching in form types forever and stumbling upon weird side effects.

I'm a bit afraid that if we accept this PR, we'll end up with PRE_SUBMIT validation listeners everywhere, as that would be bad for performances too.

I've left some minor comments but then I would be +0.

What do you think symfony deciders?

@@ -65,6 +65,22 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// To avoid some problem against treating of array (e.g. Array to string conversion),
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please move this new block at the end of the method?

}

foreach ($data as $v) {
if (is_array($v)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be:

if (null !== $v && !is_string($v)) {
    throw new TransformationFailedException('All choices submitted must be NULL or strings.');
}

@issei-m
Copy link
Contributor Author

issei-m commented Jan 19, 2017

@HeahDude Just I've fixed point of mentioned by you.

}

foreach ($data as $v) {
if (null !== $v && !is_string($v)) {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of adding a new event listener we could do this in the already existing one before flipping the data, can't we?

Copy link
Contributor Author

@issei-m issei-m Feb 9, 2017

Choose a reason for hiding this comment

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

The listener you mentioned is only added when choice is multiple. Even though we would do so we still need to add a new listener for this.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks, I missed that.

@@ -160,6 +160,22 @@ public function buildForm(FormBuilderInterface $builder, array $options)
// transformation is merged back into the original collection
$builder->addEventSubscriber(new MergeCollectionListener(true, true));
}

// To avoid some problem against treating of array (e.g. Array to string conversion),
// we have to first ensure the all elements of submitted data ain't an array.
Copy link
Member

Choose a reason for hiding this comment

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

"To avoid issues when the submitted choices are arrays (i.e. array to string conversions), we have to ensure that all elements of the submitted choice data are strings or null."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed up

@xabbuh
Copy link
Member

xabbuh commented Feb 9, 2017

One last thing that I missed during my previous review: Please move the data provider after the test method.

@issei-m
Copy link
Contributor Author

issei-m commented Feb 10, 2017

@xabbuh It's done. Thanks for your advice!

@xabbuh
Copy link
Member

xabbuh commented Feb 10, 2017

👍

Status: Reviewed

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

Thank you @issei-m.

fabpot added a commit that referenced this pull request Mar 1, 2017
…ed unnecessarily (issei-m)

This PR was squashed before being merged into the 2.7 branch (closes #21267).

Discussion
----------

[Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily

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

Fixed ChoiceType to protect against some problem caused by treating of array.

Let's say we have the choice-form like:

```php
$form = $factory->create(ChoiceType, null, [
    'choices' => [
        'A',
        'B',
        'C',
    ],
    'expanded' => true,
    'multiple' => true,
]);
```

Then, submit data like this:

```php
$form->submit([
    [], // unnecessality nested
]);
```

(Yes, I agree in most cases these situation doesn't happen, but can be)

Then, we get `array_flip(): Can only flip STRING and INTEGER values!` error at [here](https://github.com/symfony/symfony/blob/6babdb3296a5fbbd9c69d1e3410adbdf749959ef/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php#L114).
Even if form is not `multiple`, annoying `Array to string conversion` error occurs in [here](https://github.com/symfony/symfony/blob/6babdb3296a5fbbd9c69d1e3410adbdf749959ef/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php#L144) (via [ChoicesToValuesTransformer](https://github.com/symfony/symfony/blob/5129c4cf7e294b1a5ea30d6fec6e89b75396dcd2/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php#L74)).
(As far as I know, non-multiple and non-expanded form has no problem, thanks to [ChoiceToValueTransformer](https://github.com/symfony/symfony/blob/6babdb3296a5fbbd9c69d1e3410adbdf749959ef/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php#L43))

To resolve these problems, I just added a simple-validation listener to choice type.

Commits
-------

64d7a82 [Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily
@fabpot fabpot closed this Mar 1, 2017
@issei-m issei-m deleted the fix-choice-bug branch March 1, 2017 23:43
This was referenced Mar 6, 2017
fabpot added a commit that referenced this pull request Mar 10, 2017
This PR was squashed before being merged into the 2.7 branch (closes #21957).

Discussion
----------

[Form] Choice type int values (BC Fix)

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #21952
| License       | MIT

#21267 unnecessarily forced all choice values to be strings, this was a BC braking change.

Everything can work fine if they are strings or ints. specifically the issue was because `array_flip` is used on the `choices` array, but this function will work fine with ints as well as strings.

Normally, when using HTML forms all data comes as strings, but the `symfony/form` component has use cases beyond this, specifically, I use it with JSON data, where valid values for a `ChoiceType` might not be strings.

Commits
-------

ed211e9 [Form] Choice type int values (BC Fix)
chihiro-adachi pushed a commit to chihiro-adachi/ec-cube that referenced this pull request Mar 14, 2017
- symfony 3.2.5/6で、choice typeでint値を受け付けなくなっていたため(symfony/symfony#21267)
- 21267はbc break扱いで再度修正が入っている(symfony/symfony#21957)
- symfony3.2.7で解消されると思われるが、テスト通したいので直しておく
chihiro-adachi pushed a commit to chihiro-adachi/ec-cube that referenced this pull request Apr 5, 2017
…y/symfony#21267) - 21267はbc break扱いで再度修正が入っている(symfony/symfony#21957) - symfony3.2.7で解消されると思われるが、テスト通したいので直しておく
}

foreach ($data as $v) {
if (null !== $v && !is_string($v)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The is_string() check may be a little bit to restrictive in a loosely typed language like PHP.
In our code we have an event listener that adds IDs to the submitted form data. But the IDs are integers. So in Symfony 2.8.17 our processing works fine, but in 2.8.18 our processing fails. So for me the fixed code introduces a BC break. What is your opinion about that?

Copy link
Member

Choose a reason for hiding this comment

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

see #21957 which is part of the 2.8.19 release

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks :)
But 2.8.19 has some changes in DoctrineBridge that breaks our code (so we switched back to 2.18.18) :(

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