bug #18747 [2.8] [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m)

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

Discussion
----------

[2.8] [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper

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

This bug was introduced in PR #17099. So does not represent in 2.8.2 or older.

If we have the following structure form:

```php
$builder = $formFactory->createBuilder();

$form = $builder
    ->add(
        $builder->create('person1_name', FormType::class, ['inherit_data' => true])
            ->add('first', TextType::class, ['property_path' => '[person1_first_name]'])
            ->add('last', TextType::class, ['property_path' => '[person1_last_name]'])
    )
    ->add(
        $builder->create('person2_name', FormType::class, ['inherit_data' => true])
            ->add('first', TextType::class, ['property_path' => '[person2_first_name]'])
            ->add('last', TextType::class, ['property_path' => '[person2_last_name]'])
    )
    ->getForm()
;
```

The following mapping for this form doesn't work correctly:

```php
$mapper = new ViolationMapper();
$mapper->mapViolation(new ConstraintViolation('', '', [], null, 'data[person1_first_name]', null), $form);

$form['person1_name']['first']->getErrors(); // empty
$form->getErrors(); // The violation is mapped to here instead.
```

## Cause

Because ViolationMapper uses `iterator_to_array` in [here](f29d46f29b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php (L165)) to collect the sub forms.

`person1_name` and `person2_name` enable `inherit_data` option. So ViolationMapper will attempt to collect the sub forms of root form like this:

```php
[
    'first' => Form object, // root.person1_name.first
    'last'  => Form object, // root.person1_name.last
    'first' => Form object, // root.person2_name.first
    'last'  => Form object, // root.person2_name.last
]
```

As you can see, The name `first` and `last` are used in two places, thus we cannot get result like that.
(first/last of person1_name are overwritten by person2_name's)

So the violation will finally lost the form where it should map to. It should pass `false` to `iterator_to_array`'s 2nd parameter.

Commits
-------

ae38660 [2.8] [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper
This commit is contained in:
Fabien Potencier 2016-05-13 10:41:05 -05:00
commit 0753bd70ae

View File

@ -1542,24 +1542,39 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
public function testBacktrackIfSeveralSubFormsWithSamePropertyPath()
{
$violation = $this->getConstraintViolation('data.address[street]');
$parent = $this->getForm('parent');
$child1 = $this->getForm('subform1', 'address');
$child2 = $this->getForm('subform2', 'address');
$grandChild = $this->getForm('street');
$child3 = $this->getForm('subform3', null, null, array(), true);
$child4 = $this->getForm('subform4', null, null, array(), true);
$grandChild1 = $this->getForm('street');
$grandChild2 = $this->getForm('street', '[sub_address1_street]');
$grandChild3 = $this->getForm('street', '[sub_address2_street]');
$parent->add($child1);
$parent->add($child2);
$child2->add($grandChild);
$parent->add($child3);
$parent->add($child4);
$child2->add($grandChild1);
$child3->add($grandChild2);
$child4->add($grandChild3);
$parent->submit(array());
$this->mapper->mapViolation($violation, $parent);
$violation1 = $this->getConstraintViolation('data.address[street]');
$violation2 = $this->getConstraintViolation('data[sub_address1_street]');
$violation3 = $this->getConstraintViolation('data[sub_address2_street]');
$this->mapper->mapViolation($violation1, $parent);
$this->mapper->mapViolation($violation2, $parent);
$this->mapper->mapViolation($violation3, $parent);
// The error occurred on the child of the second form with the same path
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child1->getErrors(), $child1->getName().' should not have an error, but has one');
$this->assertCount(0, $child2->getErrors(), $child2->getName().' should not have an error, but has one');
$this->assertEquals(array($this->getFormError($violation, $grandChild)), iterator_to_array($grandChild->getErrors()), $grandChild->getName().' should have an error, but has none');
$this->assertCount(0, $child3->getErrors(), $child3->getName().' should not have an error, but has one');
$this->assertCount(0, $child4->getErrors(), $child4->getName().' should not have an error, but has one');
$this->assertEquals(array($this->getFormError($violation1, $grandChild1)), iterator_to_array($grandChild1->getErrors()), $grandChild1->getName().' should have an error, but has none');
$this->assertEquals(array($this->getFormError($violation2, $grandChild2)), iterator_to_array($grandChild2->getErrors()), $grandChild2->getName().' should have an error, but has none');
$this->assertEquals(array($this->getFormError($violation3, $grandChild3)), iterator_to_array($grandChild3->getErrors()), $grandChild3->getName().' should have an error, but has none');
}
}