diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 1804009d22..f2477a47c2 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -73,6 +73,8 @@ CHANGELOG * added option "mapped" which should be used instead of setting "property_path" to false * "data_class" now *must* be set if a form maps to an object and should be left empty otherwise * improved error mapping on forms + * dot (".") rules are now allowed to map errors assigned to a form to + one of its children * errors are not mapped to unsynchronized forms anymore * changed Form constructor to accept a single `FormConfigInterface` object * changed argument order in the FormBuilder constructor diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php index 56371ed198..9c87b96c54 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php @@ -90,7 +90,7 @@ class MappingRule * * @throws ErrorMappingException */ - private function getTarget() + public function getTarget() { $childNames = explode('.', $this->targetPath); $target = $this->origin; diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php index 6c8bc9c92f..195ae1b234 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -91,6 +91,15 @@ class ViolationMapper } } + // Follow dot rules until we have the final target + $mapping = $this->scope->getAttribute('error_mapping'); + + while (isset($mapping['.'])) { + $dotRule = new MappingRule($this->scope, '.', $mapping['.']); + $this->scope = $dotRule->getTarget(); + $mapping = $this->scope->getAttribute('error_mapping'); + } + // Only add the error if the form is synchronized // If the form is not synchronized, it already contains an // error about being invalid. Further errors could be a result @@ -260,7 +269,10 @@ class ViolationMapper new VirtualFormAwareIterator($form->getChildren()) ); foreach ($form->getAttribute('error_mapping') as $propertyPath => $targetPath) { - $this->rules[] = new MappingRule($form, $propertyPath, $targetPath); + // Dot rules are considered at the very end + if ('.' !== $propertyPath) { + $this->rules[] = new MappingRule($form, $propertyPath, $targetPath); + } } } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php index 317e962daf..d0674269be 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php @@ -1354,4 +1354,30 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase $this->assertFalse($parent->hasErrors(), $parent->getName() . ' should not have an error, but has one'); $this->assertFalse($child->hasErrors(), $child->getName() . ' should not have an error, but has one'); } + + public function testFollowDotRules() + { + $violation = $this->getConstraintViolation('data.foo'); + $parent = $this->getForm('parent', null, null, array( + 'foo' => 'address', + )); + $child = $this->getForm('address', null, null, array( + '.' => 'street', + )); + $grandChild = $this->getForm('street', null, null, array( + '.' => 'name', + )); + $grandGrandChild = $this->getForm('name'); + + $parent->add($child); + $child->add($grandChild); + $grandChild->add($grandGrandChild); + + $this->mapper->mapViolation($violation, $parent); + + $this->assertFalse($parent->hasErrors(), $parent->getName() . ' should not have an error, but has one'); + $this->assertFalse($child->hasErrors(), $child->getName() . ' should not have an error, but has one'); + $this->assertFalse($grandChild->hasErrors(), $grandChild->getName() . ' should not have an error, but has one'); + $this->assertEquals(array($this->getFormError()), $grandGrandChild->getErrors(), $grandGrandChild->getName() . ' should have an error, but has none'); + } }