diff options
Diffstat (limited to 'vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php')
-rw-r--r-- | vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php new file mode 100644 index 00000000..8a7636c7 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php | |||
@@ -0,0 +1,299 @@ | |||
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\InheritDataAwareIterator; | ||
16 | use Symfony\Component\PropertyAccess\PropertyPathIterator; | ||
17 | use Symfony\Component\PropertyAccess\PropertyPathBuilder; | ||
18 | use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface; | ||
19 | use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationPathIterator; | ||
20 | use Symfony\Component\Form\FormError; | ||
21 | use Symfony\Component\Validator\ConstraintViolation; | ||
22 | |||
23 | /** | ||
24 | * @author Bernhard Schussek <bschussek@gmail.com> | ||
25 | */ | ||
26 | class ViolationMapper implements ViolationMapperInterface | ||
27 | { | ||
28 | /** | ||
29 | * @var Boolean | ||
30 | */ | ||
31 | private $allowNonSynchronized; | ||
32 | |||
33 | /** | ||
34 | * {@inheritdoc} | ||
35 | */ | ||
36 | public function mapViolation(ConstraintViolation $violation, FormInterface $form, $allowNonSynchronized = false) | ||
37 | { | ||
38 | $this->allowNonSynchronized = $allowNonSynchronized; | ||
39 | |||
40 | // The scope is the currently found most specific form that | ||
41 | // an error should be mapped to. After setting the scope, the | ||
42 | // mapper will try to continue to find more specific matches in | ||
43 | // the children of scope. If it cannot, the error will be | ||
44 | // mapped to this scope. | ||
45 | $scope = null; | ||
46 | |||
47 | $violationPath = null; | ||
48 | $relativePath = null; | ||
49 | $match = false; | ||
50 | |||
51 | // Don't create a ViolationPath instance for empty property paths | ||
52 | if (strlen($violation->getPropertyPath()) > 0) { | ||
53 | $violationPath = new ViolationPath($violation->getPropertyPath()); | ||
54 | $relativePath = $this->reconstructPath($violationPath, $form); | ||
55 | } | ||
56 | |||
57 | // This case happens if the violation path is empty and thus | ||
58 | // the violation should be mapped to the root form | ||
59 | if (null === $violationPath) { | ||
60 | $scope = $form; | ||
61 | } | ||
62 | |||
63 | // In general, mapping happens from the root form to the leaf forms | ||
64 | // First, the rules of the root form are applied to determine | ||
65 | // the subsequent descendant. The rules of this descendant are then | ||
66 | // applied to find the next and so on, until we have found the | ||
67 | // most specific form that matches the violation. | ||
68 | |||
69 | // If any of the forms found in this process is not synchronized, | ||
70 | // mapping is aborted. Non-synchronized forms could not reverse | ||
71 | // transform the value entered by the user, thus any further violations | ||
72 | // caused by the (invalid) reverse transformed value should be | ||
73 | // ignored. | ||
74 | |||
75 | if (null !== $relativePath) { | ||
76 | // Set the scope to the root of the relative path | ||
77 | // This root will usually be $form. If the path contains | ||
78 | // an unmapped form though, the last unmapped form found | ||
79 | // will be the root of the path. | ||
80 | $scope = $relativePath->getRoot(); | ||
81 | $it = new PropertyPathIterator($relativePath); | ||
82 | |||
83 | while ($this->acceptsErrors($scope) && null !== ($child = $this->matchChild($scope, $it))) { | ||
84 | $scope = $child; | ||
85 | $it->next(); | ||
86 | $match = true; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | // This case happens if an error happened in the data under a | ||
91 | // form inheriting its parent data that does not match any of the | ||
92 | // children of that form. | ||
93 | if (null !== $violationPath && !$match) { | ||
94 | // If we could not map the error to anything more specific | ||
95 | // than the root element, map it to the innermost directly | ||
96 | // mapped form of the violation path | ||
97 | // e.g. "children[foo].children[bar].data.baz" | ||
98 | // Here the innermost directly mapped child is "bar" | ||
99 | |||
100 | $scope = $form; | ||
101 | $it = new ViolationPathIterator($violationPath); | ||
102 | |||
103 | // Note: acceptsErrors() will always return true for forms inheriting | ||
104 | // their parent data, because these forms can never be non-synchronized | ||
105 | // (they don't do any data transformation on their own) | ||
106 | while ($this->acceptsErrors($scope) && $it->valid() && $it->mapsForm()) { | ||
107 | if (!$scope->has($it->current())) { | ||
108 | // Break if we find a reference to a non-existing child | ||
109 | break; | ||
110 | } | ||
111 | |||
112 | $scope = $scope->get($it->current()); | ||
113 | $it->next(); | ||
114 | } | ||
115 | } | ||
116 | |||
117 | // Follow dot rules until we have the final target | ||
118 | $mapping = $scope->getConfig()->getOption('error_mapping'); | ||
119 | |||
120 | while ($this->acceptsErrors($scope) && isset($mapping['.'])) { | ||
121 | $dotRule = new MappingRule($scope, '.', $mapping['.']); | ||
122 | $scope = $dotRule->getTarget(); | ||
123 | $mapping = $scope->getConfig()->getOption('error_mapping'); | ||
124 | } | ||
125 | |||
126 | // Only add the error if the form is synchronized | ||
127 | if ($this->acceptsErrors($scope)) { | ||
128 | $scope->addError(new FormError( | ||
129 | $violation->getMessage(), | ||
130 | $violation->getMessageTemplate(), | ||
131 | $violation->getMessageParameters(), | ||
132 | $violation->getMessagePluralization() | ||
133 | )); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Tries to match the beginning of the property path at the | ||
139 | * current position against the children of the scope. | ||
140 | * | ||
141 | * If a matching child is found, it is returned. Otherwise | ||
142 | * null is returned. | ||
143 | * | ||
144 | * @param FormInterface $form The form to search. | ||
145 | * @param PropertyPathIteratorInterface $it The iterator at its current position. | ||
146 | * | ||
147 | * @return null|FormInterface The found match or null. | ||
148 | */ | ||
149 | private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it) | ||
150 | { | ||
151 | // Remember at what property path underneath "data" | ||
152 | // we are looking. Check if there is a child with that | ||
153 | // path, otherwise increase path by one more piece | ||
154 | $chunk = ''; | ||
155 | $foundChild = null; | ||
156 | $foundAtIndex = 0; | ||
157 | |||
158 | // Construct mapping rules for the given form | ||
159 | $rules = array(); | ||
160 | |||
161 | foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) { | ||
162 | // Dot rules are considered at the very end | ||
163 | if ('.' !== $propertyPath) { | ||
164 | $rules[] = new MappingRule($form, $propertyPath, $targetPath); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | // Skip forms inheriting their parent data when iterating the children | ||
169 | $childIterator = new \RecursiveIteratorIterator( | ||
170 | new InheritDataAwareIterator($form->all()) | ||
171 | ); | ||
172 | |||
173 | // Make the path longer until we find a matching child | ||
174 | while (true) { | ||
175 | if (!$it->valid()) { | ||
176 | return null; | ||
177 | } | ||
178 | |||
179 | if ($it->isIndex()) { | ||
180 | $chunk .= '['.$it->current().']'; | ||
181 | } else { | ||
182 | $chunk .= ('' === $chunk ? '' : '.').$it->current(); | ||
183 | } | ||
184 | |||
185 | // Test mapping rules as long as we have any | ||
186 | foreach ($rules as $key => $rule) { | ||
187 | /* @var MappingRule $rule */ | ||
188 | |||
189 | // Mapping rule matches completely, terminate. | ||
190 | if (null !== ($form = $rule->match($chunk))) { | ||
191 | return $form; | ||
192 | } | ||
193 | |||
194 | // Keep only rules that have $chunk as prefix | ||
195 | if (!$rule->isPrefix($chunk)) { | ||
196 | unset($rules[$key]); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | // Test children unless we already found one | ||
201 | if (null === $foundChild) { | ||
202 | foreach ($childIterator as $child) { | ||
203 | /* @var FormInterface $child */ | ||
204 | $childPath = (string) $child->getPropertyPath(); | ||
205 | |||
206 | // Child found, mark as return value | ||
207 | if ($chunk === $childPath) { | ||
208 | $foundChild = $child; | ||
209 | $foundAtIndex = $it->key(); | ||
210 | } | ||
211 | } | ||
212 | } | ||
213 | |||
214 | // Add element to the chunk | ||
215 | $it->next(); | ||
216 | |||
217 | // If we reached the end of the path or if there are no | ||
218 | // more matching mapping rules, return the found child | ||
219 | if (null !== $foundChild && (!$it->valid() || count($rules) === 0)) { | ||
220 | // Reset index in case we tried to find mapping | ||
221 | // rules further down the path | ||
222 | $it->seek($foundAtIndex); | ||
223 | |||
224 | return $foundChild; | ||
225 | } | ||
226 | } | ||
227 | |||
228 | return null; | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * Reconstructs a property path from a violation path and a form tree. | ||
233 | * | ||
234 | * @param ViolationPath $violationPath The violation path. | ||
235 | * @param FormInterface $origin The root form of the tree. | ||
236 | * | ||
237 | * @return RelativePath The reconstructed path. | ||
238 | */ | ||
239 | private function reconstructPath(ViolationPath $violationPath, FormInterface $origin) | ||
240 | { | ||
241 | $propertyPathBuilder = new PropertyPathBuilder($violationPath); | ||
242 | $it = $violationPath->getIterator(); | ||
243 | $scope = $origin; | ||
244 | |||
245 | // Remember the current index in the builder | ||
246 | $i = 0; | ||
247 | |||
248 | // Expand elements that map to a form (like "children[address]") | ||
249 | for ($it->rewind(); $it->valid() && $it->mapsForm(); $it->next()) { | ||
250 | if (!$scope->has($it->current())) { | ||
251 | // Scope relates to a form that does not exist | ||
252 | // Bail out | ||
253 | break; | ||
254 | } | ||
255 | |||
256 | // Process child form | ||
257 | $scope = $scope->get($it->current()); | ||
258 | |||
259 | if ($scope->getConfig()->getInheritData()) { | ||
260 | // Form inherits its parent data | ||
261 | // Cut the piece out of the property path and proceed | ||
262 | $propertyPathBuilder->remove($i); | ||
263 | } elseif (!$scope->getConfig()->getMapped()) { | ||
264 | // Form is not mapped | ||
265 | // Set the form as new origin and strip everything | ||
266 | // we have so far in the path | ||
267 | $origin = $scope; | ||
268 | $propertyPathBuilder->remove(0, $i + 1); | ||
269 | $i = 0; | ||
270 | } else { | ||
271 | /* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */ | ||
272 | $propertyPath = $scope->getPropertyPath(); | ||
273 | |||
274 | if (null === $propertyPath) { | ||
275 | // Property path of a mapped form is null | ||
276 | // Should not happen, bail out | ||
277 | break; | ||
278 | } | ||
279 | |||
280 | $propertyPathBuilder->replace($i, 1, $propertyPath); | ||
281 | $i += $propertyPath->getLength(); | ||
282 | } | ||
283 | } | ||
284 | |||
285 | $finalPath = $propertyPathBuilder->getPropertyPath(); | ||
286 | |||
287 | return null !== $finalPath ? new RelativePath($origin, $finalPath) : null; | ||
288 | } | ||
289 | |||
290 | /** | ||
291 | * @param FormInterface $form | ||
292 | * | ||
293 | * @return Boolean | ||
294 | */ | ||
295 | private function acceptsErrors(FormInterface $form) | ||
296 | { | ||
297 | return $this->allowNonSynchronized || $form->isSynchronized(); | ||
298 | } | ||
299 | } | ||