8000 [Form] Greatly improved the error mapping done in DelegatingValidatio… · symfony/symfony@306324e · GitHub
[go: up one dir, main page]

Skip to content

Commit 306324e

Browse files
committed
[Form] Greatly improved the error mapping done in DelegatingValidationListener
1 parent 8f7e2f6 commit 306324e

17 files changed

+2769
-641
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Exception;
13+
14+
class ErrorMappingException extends FormException
15+
{
16+
}

src/Symfony/Component/Form/Extension/Validator/EventListener/DelegatingValidationListener.php

Lines changed: 6 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Form\Extension\Validator\EventListener;
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
1516
use Symfony\Component\Form\FormInterface;
1617
use Symfony\Component\Form\FormError;
1718
use Symfony\Component\Form\FormEvents;
@@ -129,14 +130,6 @@ public function validateForm(DataEvent $event)
129130
$form = $event->getForm();
130131

131132
if ($form->isRoot()) {
132-
$mapping = array();
133-
$forms = array();
134-
135-
$this->buildFormPathMapping($form, $mapping);
136-
$this->buildDataPathMapping($form, $mapping);
137-
$this->buildNamePathMapping($form, $forms);
138-
$this->resolveMappingPlaceholders($mapping, $forms);
139-
140133
// Validate the form in group "Default"
141134
// Validation of the data in the custom group is done by validateData(),
142135
// which is constrained by the Execute constraint
@@ -146,149 +139,16 @@ public function validateForm(DataEvent $event)
146139
$form->getAttribute('validation_constraint'),
147140
self::getFormValidationGroups($form)
148141
);
149-
150-
if ($violations) {
151-
foreach ($violations as $violation) {
152-
$propertyPath = new PropertyPath($violation->getPropertyPath());
153-
$template = $violation->getMessageTemplate();
154-
$parameters = $violation->getMessageParameters();
155-
$pluralization = $violation->getMessagePluralization();
156-
$error = new FormError($template, $parameters, $pluralization);
157-
158-
$child = $form;
159-
foreach ($propertyPath->getElements() as $element) {
160-
$children = $child->getChildren();
161-
if (!isset($children[$element])) {
162-
$form->addError($error);
163-
break;
164-
}
165-
166-
$child = $children[$element];
167-
}
168-
169-
$child->addError($error);
170-
}
171-
}
172-
} elseif (count($violations = $this->validator->validate($form))) {
173-
foreach ($violations as $violation) {
174-
$propertyPath = $violation->getPropertyPath();
175-
$template = $violation->getMessageTemplate();
176-
$parameters = $violation->getMessageParameters();
177-
$pluralization = $violation->getMessagePluralization();
178-
$error = new FormError($template, $parameters, $pluralization);
179-
180-
foreach ($mapping as $mappedPath => $child) {
181-
if (preg_match($mappedPath, $propertyPath)) {
182-
$child->addError($error);
183-
continue 2;
184-
}
185-
}
186-
187-
$form->addError($error);
188-
}
189-
}
190-
}
191-
}
192-
193-
private function buildFormPathMapping(FormInterface $form, array &$mapping, $formPath = 'children', $namePath = '')
194-
{
195-
foreach ($form->getAttribute('error_mapping') as $nestedDataPath => $nestedNamePath) {
196-
$mapping['/^'.preg_quote($formPath.'.data.'.$nestedDataPath).'(?!\w)/'] = $namePath.'.'.$nestedNamePath;
197-
}
198-
199-
$iterator = new VirtualFormAwareIterator($form->getChildren());
200-
$iterator = new \RecursiveIteratorIterator($iterator);
201-
202-
foreach ($iterator as $child) {
203-
$path = (string) $child->getAttribute('property_path');
204-
$parts = explode('.', $path, 2);
205-
206-
$nestedNamePath = $namePath.'.'.$child->getName();
207-
208-
if ($child->hasChildren() || isset($parts[1])) {
209-
$nestedFormPath = $formPath.'['.trim($parts[0], '[]').']';
210142
} else {
211-
$nestedFormPath = $formPath.'.data.'.$parts[0];
212-
}
213-
214-
if (isset($parts[1])) {
215-
$nestedFormPath .= '.data.'.$parts[1];
143+
$violations = $this->validator->validate($form);
216144
}
217145

218-
if ($child->hasChildren()) {
219-
$this->buildFormPathMapping($child, $mapping, $nestedFormPath, $nestedNamePath);
220-
}
146+
if (count($violations) > 0) {
147+
$mapper = new ViolationMapper();
221148

222-
$mapping['/^'.preg_quote($nestedFormPath, '/').'(?!\w)/'] = $child;
223-
}
224-
}
225-
226-
private function buildDataPathMapping(FormInterface $form, array &$mapping, $dataPath = 'data', $namePath = '')
227-
{
228-
foreach ($form->getAttribute('error_mapping') as $nestedDataPath => $nestedNamePath) {
229-
$mapping['/^'.preg_quote($dataPath.'.'.$nestedDataPath).'(?!\w)/'] = $namePath.'.'.$nestedNamePath;
230-
}
231-
232-
$iterator = new VirtualFormAwareIterator($form->getChildren());
233-
$iterator = new \RecursiveIteratorIterator($iterator);
234-
235-
foreach ($iterator as $child) {
236-
$path = (string) $child->getAttribute('property_path');
237-
238-
$nestedNamePath = $namePath.'.'.$child->getName();
239-
240-
if (0 === strpos($path, '[')) {
241-
$nestedDataPaths = array($dataPath.$path);
242-
} else {
243-
$nestedDataPaths = array($dataPath.'.'.$path);
244-
if ($child->hasChildren()) {
245-
$nestedDataPaths[] = $dataPath.'['.$path.']';
246-
}
247-
}
248-
249-
if ($child->hasChildren()) {
250-
// Needs when collection implements the Iterator
251-
// or for array used the Valid validator.
252-
if (is_array($child->getData()) || $child->getData() instanceof \Traversable) {
253-
$this->buildDataPathMapping($child, $mapping, $dataPath, $nestedNamePath);
254-
}
255-
256-
foreach ($nestedDataPaths as $nestedDataPath) {
257-
$this->buildDataPathMapping($child, $mapping, $nestedDataPath, $nestedNamePath);
258-
}
259-
}
260-
261-
foreach ($nestedDataPaths as $nestedDataPath) {
262-
$mapping['/^'.preg_quote($nestedDataPath, '/').'(?!\w)/'] = $child;
263-
}
264-
}
265-
}
266-
267-
private function buildNamePathMapping(FormInterface $form, array &$forms, $namePath = '')
268-
{
269-
$iterator = new VirtualFormAwareIterator($form->getChildren());
270-
$iterator = new \RecursiveIteratorIterator($iterator);
271-
272-
foreach ($iterator as $child) {
273-
$nestedNamePath = $namePath.'.'.$child->getName();
274-
$forms[$nestedNamePath] = $child;
275-
276-
if ($child->hasChildren()) {
277-
$this->buildNamePathMapping($child, $forms, $nestedNamePath);
278-
}
279-
280-
}
281-
}
282-
283-
private function resolveMappingPlaceholders(array &$mapping, array $forms)
284-
{
285-
foreach ($mapping as $pattern => $form) {
286-
if (is_string($form)) {
287-
if (!isset($forms[$form])) {
288-
throw new FormException(sprintf('The child form with path "%s" does not exist', $form));
149+
foreach ($violations as $violation) {
150+
$mapper->mapViolation($violation, $form);
289151
}
290-
291-
$mapping[$pattern] = $forms[$form];
292152
}
293153
}
294154
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
13+
14+
use Symfony\Component\Form\FormInterface;
15+
use Symfony\Component\Form\Exception\ErrorMappingException;
16+
17+
/**
18+
* @author Bernhard Schussek <bschussek@gmail.com>
19+
*/
20+
class FormMapping
21+
{
22+
/**
23+
* @var FormInterface
24+
*/
25+
private $origin;
26+
27+
/**
28+
* @var FormInterface
29+
*/
30+
private $target;
31+
32+
/**
33+
* @var string
34+
*/
35+
private $targetPath;
36+
37+
public function __construct(FormInterface $origin, $targetPath)
38+
{
39+
$this->origin = $origin;
40+
$this->targetPath = $targetPath;
41+
}
42+
43+
/**
44+
* @return FormInterface
45+
*/
46+
public function getOrigin()
47+
{
48+
return $this->origin;
49+
}
50+
51+
/**
52+
* @return FormInterface
53+
*
54+
* @throws ErrorMappingException
55+
*/
56+
public function getTarget()
57+
{
58+
// Lazy initialization to make sure that the constructor is cheap
59+
if (null === $this->target) {
60+
$childNames = explode('.', $this->targetPath);
61+
$target = $this->origin;
62+
63+
foreach ($childNames as $childName) {
64+
if (!$target->has($childName)) {
65+
throw new ErrorMappingException(sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName()));
66+
}
67+
$target = $target->get($childName);
68+
}
69+
70+
// Only set once successfully resolved
71+
$this->target = $target;
72+
}
73+
74+
return $this->target;
75+
}
76+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
13+
14+
use Symfony\Component\Form\FormInterface;
15+
use Symfony\Component\Form\Util\PropertyPath;
16+
17+
/**
18+
* @author Bernhard Schussek <bschussek@gmail.com>
19+
*/
20+
class RelativePath extends PropertyPath
21+
{
22+
/**
23+
* @var FormInterface
24+
*/
25+
private $root;
26+
27+
/**
28+
* @param FormInterface $root
29+
* @param string $propertyPath
30+
*/
31+
public function __construct(FormInterface $root, $propertyPath)
32+
{
33+
parent::__construct($propertyPath);
34+
35+
$this->root = $root;
36+
}
37+
38+
/**
39+
* @return FormInterface
40+
*/
41+
public function getRoot()
42+
{
43+
return $this->root;
44+
}
45+
}

0 commit comments

Comments
 (0)
0