Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge branch '2.1'

* 2.1:
  fixed CS
  added doc comments
  added doc comments
  [Validator] Updated swedish translation
  Update src/Symfony/Component/Validator/Resources/translations/validators.de.xlf
  [2.1] Exclude tests from zips via gitattributes
  [HttpKernel][Translator] Fixed type-hints
  Updated lithuanian validation translation
  [DomCrawler] Allows using multiselect through Form::setValues().
  [Translation] forced the catalogue to be regenerated when a resource is added (closes symfony/Translation#1)
  Unit test for patched method OptionsResolver::validateOptionValues().
  validateOptionValues throw a notice if an allowed value is set and the corresponding option isn't.
  [Form] Hardened code of ViolationMapper against errors
  [HttpFoundation] Fixed #5611 - Request::splitHttpAcceptHeader incorrect result order.
  [Form] Fixed negative index access in PropertyPathBuilder
  Update src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf

Conflicts:
	src/Symfony/Component/DomCrawler/Form.php
	src/Symfony/Component/Process/Process.php
  • Loading branch information...
commit 404977be991ad456f37581ec26dfc65551fe8105 2 parents 66555f0 + b7e7fef
Fabien Potencier fabpot authored
2  .gitattributes
... ... @@ -0,0 +1,2 @@
  1 +Tests/ export-ignore
  2 +phpunit.xml.dist export-ignore
112 Extension/Validator/ViolationMapper/ViolationMapper.php
@@ -26,21 +26,6 @@
26 26 class ViolationMapper implements ViolationMapperInterface
27 27 {
28 28 /**
29   - * @var FormInterface
30   - */
31   - private $scope;
32   -
33   - /**
34   - * @var array
35   - */
36   - private $children;
37   -
38   - /**
39   - * @var array
40   - */
41   - private $rules = array();
42   -
43   - /**
44 29 * @var Boolean
45 30 */
46 31 private $allowNonSynchronized;
@@ -52,6 +37,13 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
52 37 {
53 38 $this->allowNonSynchronized = $allowNonSynchronized;
54 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 +
55 47 $violationPath = null;
56 48 $relativePath = null;
57 49 $match = false;
@@ -65,7 +57,7 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
65 57 // This case happens if the violation path is empty and thus
66 58 // the violation should be mapped to the root form
67 59 if (null === $violationPath) {
68   - $this->scope = $form;
  60 + $scope = $form;
69 61 }
70 62
71 63 // In general, mapping happens from the root form to the leaf forms
@@ -85,11 +77,11 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
85 77 // This root will usually be $form. If the path contains
86 78 // an unmapped form though, the last unmapped form found
87 79 // will be the root of the path.
88   - $this->setScope($relativePath->getRoot());
  80 + $scope = $relativePath->getRoot();
89 81 $it = new PropertyPathIterator($relativePath);
90 82
91   - while ($this->isValidScope() && null !== ($child = $this->matchChild($it))) {
92   - $this->setScope($child);
  83 + while ($this->acceptsErrors($scope) && null !== ($child = $this->matchChild($scope, $it))) {
  84 + $scope = $child;
93 85 $it->next();
94 86 $match = true;
95 87 }
@@ -105,33 +97,32 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
105 97 // e.g. "children[foo].children[bar].data.baz"
106 98 // Here the innermost directly mapped child is "bar"
107 99
  100 + $scope = $form;
108 101 $it = new ViolationPathIterator($violationPath);
109   - // The overhead of setScope() is not needed anymore here
110   - $this->scope = $form;
111 102
112   - while ($this->isValidScope() && $it->valid() && $it->mapsForm()) {
113   - if (!$this->scope->has($it->current())) {
  103 + while ($this->acceptsErrors($scope) && $it->valid() && $it->mapsForm()) {
  104 + if (!$scope->has($it->current())) {
114 105 // Break if we find a reference to a non-existing child
115 106 break;
116 107 }
117 108
118   - $this->scope = $this->scope->get($it->current());
  109 + $scope = $scope->get($it->current());
119 110 $it->next();
120 111 }
121 112 }
122 113
123 114 // Follow dot rules until we have the final target
124   - $mapping = $this->scope->getConfig()->getOption('error_mapping');
  115 + $mapping = $scope->getConfig()->getOption('error_mapping');
125 116
126   - while ($this->isValidScope() && isset($mapping['.'])) {
127   - $dotRule = new MappingRule($this->scope, '.', $mapping['.']);
128   - $this->scope = $dotRule->getTarget();
129   - $mapping = $this->scope->getConfig()->getOption('error_mapping');
  117 + while ($this->acceptsErrors($scope) && isset($mapping['.'])) {
  118 + $dotRule = new MappingRule($scope, '.', $mapping['.']);
  119 + $scope = $dotRule->getTarget();
  120 + $mapping = $scope->getConfig()->getOption('error_mapping');
130 121 }
131 122
132 123 // Only add the error if the form is synchronized
133   - if ($this->isValidScope()) {
134   - $this->scope->addError(new FormError(
  124 + if ($this->acceptsErrors($scope)) {
  125 + $scope->addError(new FormError(
135 126 $violation->getMessageTemplate(),
136 127 $violation->getMessageParameters(),
137 128 $violation->getMessagePluralization()
@@ -146,11 +137,12 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
146 137 * If a matching child is found, it is returned. Otherwise
147 138 * null is returned.
148 139 *
149   - * @param PropertyPathIteratorInterface $it The iterator at its current position.
  140 + * @param FormInterface $form The form to search.
  141 + * @param PropertyPathIteratorInterface $it The iterator at its current position.
150 142 *
151 143 * @return null|FormInterface The found match or null.
152 144 */
153   - private function matchChild(PropertyPathIteratorInterface $it)
  145 + private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it)
154 146 {
155 147 // Remember at what property path underneath "data"
156 148 // we are looking. Check if there is a child with that
@@ -159,6 +151,21 @@ private function matchChild(PropertyPathIteratorInterface $it)
159 151 $foundChild = null;
160 152 $foundAtIndex = 0;
161 153
  154 + // Construct mapping rules for the given form
  155 + $rules = array();
  156 +
  157 + foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) {
  158 + // Dot rules are considered at the very end
  159 + if ('.' !== $propertyPath) {
  160 + $rules[] = new MappingRule($form, $propertyPath, $targetPath);
  161 + }
  162 + }
  163 +
  164 + // Ignore virtual forms when iterating the children
  165 + $childIterator = new \RecursiveIteratorIterator(
  166 + new VirtualFormAwareIterator($form->all())
  167 + );
  168 +
162 169 // Make the path longer until we find a matching child
163 170 while (true) {
164 171 if (!$it->valid()) {
@@ -172,7 +179,7 @@ private function matchChild(PropertyPathIteratorInterface $it)
172 179 }
173 180
174 181 // Test mapping rules as long as we have any
175   - foreach ($this->rules as $key => $rule) {
  182 + foreach ($rules as $key => $rule) {
176 183 /* @var MappingRule $rule */
177 184
178 185 // Mapping rule matches completely, terminate.
@@ -182,17 +189,17 @@ private function matchChild(PropertyPathIteratorInterface $it)
182 189
183 190 // Keep only rules that have $chunk as prefix
184 191 if (!$rule->isPrefix($chunk)) {
185   - unset($this->rules[$key]);
  192 + unset($rules[$key]);
186 193 }
187 194 }
188 195
189 196 // Test children unless we already found one
190 197 if (null === $foundChild) {
191   - foreach ($this->children as $child) {
  198 + foreach ($childIterator as $child) {
192 199 /* @var FormInterface $child */
193 200 $childPath = (string) $child->getPropertyPath();
194 201
195   - // Child found, move scope inwards
  202 + // Child found, mark as return value
196 203 if ($chunk === $childPath) {
197 204 $foundChild = $child;
198 205 $foundAtIndex = $it->key();
@@ -205,7 +212,7 @@ private function matchChild(PropertyPathIteratorInterface $it)
205 212
206 213 // If we reached the end of the path or if there are no
207 214 // more matching mapping rules, return the found child
208   - if (null !== $foundChild && (!$it->valid() || count($this->rules) === 0)) {
  215 + if (null !== $foundChild && (!$it->valid() || count($rules) === 0)) {
209 216 // Reset index in case we tried to find mapping
210 217 // rules further down the path
211 218 $it->seek($foundAtIndex);
@@ -277,35 +284,12 @@ private function reconstructPath(ViolationPath $violationPath, FormInterface $or
277 284 }
278 285
279 286 /**
280   - * Sets the scope of the mapper to the given form.
281   - *
282   - * The scope is the currently found most specific form that
283   - * an error should be mapped to. After setting the scope, the
284   - * mapper will try to continue to find more specific matches in
285   - * the children of scope. If it cannot, the error will be
286   - * mapped to this scope.
  287 + * @param FormInterface $form
287 288 *
288   - * @param FormInterface $form The current scope.
289   - */
290   - private function setScope(FormInterface $form)
291   - {
292   - $this->scope = $form;
293   - $this->children = new \RecursiveIteratorIterator(
294   - new VirtualFormAwareIterator($form->all())
295   - );
296   - foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) {
297   - // Dot rules are considered at the very end
298   - if ('.' !== $propertyPath) {
299   - $this->rules[] = new MappingRule($form, $propertyPath, $targetPath);
300   - }
301   - }
302   - }
303   -
304   - /**
305 289 * @return Boolean
306 290 */
307   - private function isValidScope()
  291 + private function acceptsErrors(FormInterface $form)
308 292 {
309   - return $this->allowNonSynchronized || $this->scope->isSynchronized();
  293 + return $this->allowNonSynchronized || $form->isSynchronized();
310 294 }
311 295 }
35 Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php
@@ -235,6 +235,7 @@ public function provideDefaultTests()
235 235
236 236 array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address].children[street].data'),
237 237 array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address].children[street].data.prop'),
  238 + array(self::LEVEL_1, 'address', 'address', 'street', 'street', 'children[address].data'),
238 239 array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address].data.street'),
239 240 array(self::LEVEL_2, 'address', 'address', 'street', 'street', 'children[address].data.street.prop'),
240 241 array(self::LEVEL_1, 'address', 'address', 'street', 'street', 'children[address].data[street]'),
@@ -250,6 +251,7 @@ public function provideDefaultTests()
250 251
251 252 array(self::LEVEL_2, 'address', 'address', 'street', '[street]', 'children[address].children[street].data'),
252 253 array(self::LEVEL_2, 'address', 'address', 'street', '[street]', 'children[address].children[street].data.prop'),
  254 + array(self::LEVEL_1, 'address', 'address', 'street', '[street]', 'children[address].data'),
253 255 array(self::LEVEL_1, 'address', 'address', 'street', '[street]', 'children[address].data.street'),
254 256 array(self::LEVEL_1, 'address', 'address', 'street', '[street]', 'children[address].data.street.prop'),
255 257 array(self::LEVEL_2, 'address', 'address', 'street', '[street]', 'children[address].data[street]'),
@@ -265,6 +267,7 @@ public function provideDefaultTests()
265 267
266 268 array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address].children[street].data'),
267 269 array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address].children[street].data.prop'),
  270 + array(self::LEVEL_1, 'address', '[address]', 'street', 'street', 'children[address].data'),
268 271 array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address].data.street'),
269 272 array(self::LEVEL_2, 'address', '[address]', 'street', 'street', 'children[address].data.street.prop'),
270 273 array(self::LEVEL_1, 'address', '[address]', 'street', 'street', 'children[address].data[street]'),
@@ -280,6 +283,7 @@ public function provideDefaultTests()
280 283
281 284 array(self::LEVEL_2, 'address', '[address]', 'street', '[street]', 'children[address].children[street].data'),
282 285 array(self::LEVEL_2, 'address', '[address]', 'street', '[street]', 'children[address].children[street].data.prop'),
  286 + array(self::LEVEL_1, 'address', '[address]', 'street', '[street]', 'children[address].data'),
283 287 array(self::LEVEL_1, 'address', '[address]', 'street', '[street]', 'children[address].data.street'),
284 288 array(self::LEVEL_1, 'address', '[address]', 'street', '[street]', 'children[address].data.street.prop'),
285 289 array(self::LEVEL_2, 'address', '[address]', 'street', '[street]', 'children[address].data[street]'),
@@ -295,6 +299,7 @@ public function provideDefaultTests()
295 299
296 300 array(self::LEVEL_2, 'address', 'person.address', 'street', 'street', 'children[address].children[street].data'),
297 301 array(self::LEVEL_2, 'address', 'person.address', 'street', 'street', 'children[address].children[street].data.prop'),
  302 + array(self::LEVEL_1, 'address', 'person.address', 'street', 'street', 'children[address].data'),
298 303 array(self::LEVEL_2, 'address', 'person.address', 'street', 'street', 'children[address].data.street'),
299 304 array(self::LEVEL_2, 'address', 'person.address', 'street', 'street', 'children[address].data.street.prop'),
300 305 array(self::LEVEL_1, 'address', 'person.address', 'street', 'street', 'children[address].data[street]'),
@@ -318,6 +323,7 @@ public function provideDefaultTests()
318 323
319 324 array(self::LEVEL_2, 'address', 'person.address', 'street', '[street]', 'children[address].children[street].data'),
320 325 array(self::LEVEL_2, 'address', 'person.address', 'street', '[street]', 'children[address].children[street].data.prop'),
  326 + array(self::LEVEL_1, 'address', 'person.address', 'street', '[street]', 'children[address].data'),
321 327 array(self::LEVEL_1, 'address', 'person.address', 'street', '[street]', 'children[address].data.street'),
322 328 array(self::LEVEL_1, 'address', 'person.address', 'street', '[street]', 'children[address].data.street.prop'),
323 329 array(self::LEVEL_2, 'address', 'person.address', 'street', '[street]', 'children[address].data[street]'),
@@ -341,6 +347,7 @@ public function provideDefaultTests()
341 347
342 348 array(self::LEVEL_2, 'address', 'person[address]', 'street', 'street', 'children[address].children[street].data'),
343 349 array(self::LEVEL_2, 'address', 'person[address]', 'street', 'street', 'children[address].children[street].data.prop'),
  350 + array(self::LEVEL_1, 'address', 'person[address]', 'street', 'street', 'children[address].data'),
344 351 array(self::LEVEL_2, 'address', 'person[address]', 'street', 'street', 'children[address].data.street'),
345 352 array(self::LEVEL_2, 'address', 'person[address]', 'street', 'street', 'children[address].data.street.prop'),
346 353 array(self::LEVEL_1, 'address', 'person[address]', 'street', 'street', 'children[address].data[street]'),
@@ -364,6 +371,7 @@ public function provideDefaultTests()
364 371
365 372 array(self::LEVEL_2, 'address', 'person[address]', 'street', '[street]', 'children[address].children[street].data'),
366 373 array(self::LEVEL_2, 'address', 'person[address]', 'street', '[street]', 'children[address].children[street].data.prop'),
  374 + array(self::LEVEL_1, 'address', 'person[address]', 'street', '[street]', 'children[address].data'),
367 375 array(self::LEVEL_1, 'address', 'person[address]', 'street', '[street]', 'children[address].data.street'),
368 376 array(self::LEVEL_1, 'address', 'person[address]', 'street', '[street]', 'children[address].data.street.prop'),
369 377 array(self::LEVEL_2, 'address', 'person[address]', 'street', '[street]', 'children[address].data[street]'),
@@ -387,6 +395,7 @@ public function provideDefaultTests()
387 395
388 396 array(self::LEVEL_2, 'address', '[person].address', 'street', 'street', 'children[address].children[street].data'),
389 397 array(self::LEVEL_2, 'address', '[person].address', 'street', 'street', 'children[address].children[street].data.prop'),
  398 + array(self::LEVEL_1, 'address', '[person].address', 'street', 'street', 'children[address].data'),
390 399 array(self::LEVEL_2, 'address', '[person].address', 'street', 'street', 'children[address].data.street'),
391 400 array(self::LEVEL_2, 'address', '[person].address', 'street', 'street', 'children[address].data.street.prop'),
392 401 array(self::LEVEL_1, 'address', '[person].address', 'street', 'street', 'children[address].data[street]'),
@@ -410,6 +419,7 @@ public function provideDefaultTests()
410 419
411 420 array(self::LEVEL_2, 'address', '[person].address', 'street', '[street]', 'children[address].children[street].data'),
412 421 array(self::LEVEL_2, 'address', '[person].address', 'street', '[street]', 'children[address].children[street].data.prop'),
  422 + array(self::LEVEL_1, 'address', '[person].address', 'street', '[street]', 'children[address].data'),
413 423 array(self::LEVEL_1, 'address', '[person].address', 'street', '[street]', 'children[address].data.street'),
414 424 array(self::LEVEL_1, 'address', '[person].address', 'street', '[street]', 'children[address].data.street.prop'),
415 425 array(self::LEVEL_2, 'address', '[person].address', 'street', '[street]', 'children[address].data[street]'),
@@ -433,6 +443,8 @@ public function provideDefaultTests()
433 443
434 444 array(self::LEVEL_2, 'address', '[person][address]', 'street', 'street', 'children[address].children[street].data'),
435 445 array(self::LEVEL_2, 'address', '[person][address]', 'street', 'street', 'children[address].children[street].data.prop'),
  446 + array(self::LEVEL_1, 'address', '[person][address]', 'street', 'street', 'children[address]'),
  447 + array(self::LEVEL_1, 'address', '[person][address]', 'street', 'street', 'children[address].data'),
436 448 array(self::LEVEL_2, 'address', '[person][address]', 'street', 'street', 'children[address].data.street'),
437 449 array(self::LEVEL_2, 'address', '[person][address]', 'street', 'street', 'children[address].data.street.prop'),
438 450 array(self::LEVEL_1, 'address', '[person][address]', 'street', 'street', 'children[address].data[street]'),
@@ -456,6 +468,7 @@ public function provideDefaultTests()
456 468
457 469 array(self::LEVEL_2, 'address', '[person][address]', 'street', '[street]', 'children[address].children[street].data'),
458 470 array(self::LEVEL_2, 'address', '[person][address]', 'street', '[street]', 'children[address].children[street].data.prop'),
  471 + array(self::LEVEL_1, 'address', '[person][address]', 'street', '[street]', 'children[address].data'),
459 472 array(self::LEVEL_1, 'address', '[person][address]', 'street', '[street]', 'children[address].data.street'),
460 473 array(self::LEVEL_1, 'address', '[person][address]', 'street', '[street]', 'children[address].data.street.prop'),
461 474 array(self::LEVEL_2, 'address', '[person][address]', 'street', '[street]', 'children[address].data[street]'),
@@ -479,10 +492,13 @@ public function provideDefaultTests()
479 492
480 493 array(self::LEVEL_2, 'address', 'address', 'street', 'office.street', 'children[address].children[street].data'),
481 494 array(self::LEVEL_2, 'address', 'address', 'street', 'office.street', 'children[address].children[street].data.prop'),
  495 + array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data'),
  496 + array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data.office'),
482 497 array(self::LEVEL_2, 'address', 'address', 'street', 'office.street', 'children[address].data.office.street'),
483 498 array(self::LEVEL_2, 'address', 'address', 'street', 'office.street', 'children[address].data.office.street.prop'),
484 499 array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data.office[street]'),
485 500 array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data.office[street].prop'),
  501 + array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data[office]'),
486 502 array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data[office].street'),
487 503 array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data[office].street.prop'),
488 504 array(self::LEVEL_1, 'address', 'address', 'street', 'office.street', 'children[address].data[office][street]'),
@@ -506,10 +522,13 @@ public function provideDefaultTests()
506 522
507 523 array(self::LEVEL_2, 'address', '[address]', 'street', 'office.street', 'children[address].children[street].data'),
508 524 array(self::LEVEL_2, 'address', '[address]', 'street', 'office.street', 'children[address].children[street].data.prop'),
  525 + array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data'),
  526 + array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data.office'),
509 527 array(self::LEVEL_2, 'address', '[address]', 'street', 'office.street', 'children[address].data.office.street'),
510 528 array(self::LEVEL_2, 'address', '[address]', 'street', 'office.street', 'children[address].data.office.street.prop'),
511 529 array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data.office[street]'),
512 530 array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data.office[street].prop'),
  531 + array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data[office]'),
513 532 array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data[office].street'),
514 533 array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data[office].street.prop'),
515 534 array(self::LEVEL_1, 'address', '[address]', 'street', 'office.street', 'children[address].data[office][street]'),
@@ -533,10 +552,13 @@ public function provideDefaultTests()
533 552
534 553 array(self::LEVEL_2, 'address', 'address', 'street', 'office[street]', 'children[address].children[street].data'),
535 554 array(self::LEVEL_2, 'address', 'address', 'street', 'office[street]', 'children[address].children[street].data.prop'),
  555 + array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data'),
  556 + array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data.office'),
536 557 array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data.office.street'),
537 558 array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data.office.street.prop'),
538 559 array(self::LEVEL_2, 'address', 'address', 'street', 'office[street]', 'children[address].data.office[street]'),
539 560 array(self::LEVEL_2, 'address', 'address', 'street', 'office[street]', 'children[address].data.office[street].prop'),
  561 + array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data[office]'),
540 562 array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data[office].street'),
541 563 array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data[office].street.prop'),
542 564 array(self::LEVEL_1, 'address', 'address', 'street', 'office[street]', 'children[address].data[office][street]'),
@@ -564,6 +586,7 @@ public function provideDefaultTests()
564 586 array(self::LEVEL_1, 'address', '[address]', 'street', 'office[street]', 'children[address].data.office.street.prop'),
565 587 array(self::LEVEL_2, 'address', '[address]', 'street', 'office[street]', 'children[address].data.office[street]'),
566 588 array(self::LEVEL_2, 'address', '[address]', 'street', 'office[street]', 'children[address].data.office[street].prop'),
  589 + array(self::LEVEL_1, 'address', '[address]', 'street', 'office[street]', 'children[address].data[office]'),
567 590 array(self::LEVEL_1, 'address', '[address]', 'street', 'office[street]', 'children[address].data[office].street'),
568 591 array(self::LEVEL_1, 'address', '[address]', 'street', 'office[street]', 'children[address].data[office].street.prop'),
569 592 array(self::LEVEL_1, 'address', '[address]', 'street', 'office[street]', 'children[address].data[office][street]'),
@@ -587,10 +610,13 @@ public function provideDefaultTests()
587 610
588 611 array(self::LEVEL_2, 'address', 'address', 'street', '[office].street', 'children[address].children[street].data'),
589 612 array(self::LEVEL_2, 'address', 'address', 'street', '[office].street', 'children[address].children[street].data.prop'),
  613 + array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data'),
  614 + array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data.office'),
590 615 array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data.office.street'),
591 616 array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data.office.street.prop'),
592 617 array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data.office[street]'),
593 618 array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data.office[street].prop'),
  619 + array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data[office]'),
594 620 array(self::LEVEL_2, 'address', 'address', 'street', '[office].street', 'children[address].data[office].street'),
595 621 array(self::LEVEL_2, 'address', 'address', 'street', '[office].street', 'children[address].data[office].street.prop'),
596 622 array(self::LEVEL_1, 'address', 'address', 'street', '[office].street', 'children[address].data[office][street]'),
@@ -614,10 +640,13 @@ public function provideDefaultTests()
614 640
615 641 array(self::LEVEL_2, 'address', '[address]', 'street', '[office].street', 'children[address].children[street].data'),
616 642 array(self::LEVEL_2, 'address', '[address]', 'street', '[office].street', 'children[address].children[street].data.prop'),
  643 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data'),
  644 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data.office'),
617 645 array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data.office.street'),
618 646 array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data.office.street.prop'),
619 647 array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data.office[street]'),
620 648 array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data.office[street].prop'),
  649 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data[office]'),
621 650 array(self::LEVEL_2, 'address', '[address]', 'street', '[office].street', 'children[address].data[office].street'),
622 651 array(self::LEVEL_2, 'address', '[address]', 'street', '[office].street', 'children[address].data[office].street.prop'),
623 652 array(self::LEVEL_1, 'address', '[address]', 'street', '[office].street', 'children[address].data[office][street]'),
@@ -641,10 +670,13 @@ public function provideDefaultTests()
641 670
642 671 array(self::LEVEL_2, 'address', 'address', 'street', '[office][street]', 'children[address].children[street].data'),
643 672 array(self::LEVEL_2, 'address', 'address', 'street', '[office][street]', 'children[address].children[street].data.prop'),
  673 + array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data'),
  674 + array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data.office'),
644 675 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data.office.street'),
645 676 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data.office.street.prop'),
646 677 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data.office[street]'),
647 678 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data.office[street].prop'),
  679 + array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data[office]'),
648 680 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data[office].street'),
649 681 array(self::LEVEL_1, 'address', 'address', 'street', '[office][street]', 'children[address].data[office].street.prop'),
650 682 array(self::LEVEL_2, 'address', 'address', 'street', '[office][street]', 'children[address].data[office][street]'),
@@ -668,10 +700,13 @@ public function provideDefaultTests()
668 700
669 701 array(self::LEVEL_2, 'address', '[address]', 'street', '[office][street]', 'children[address].children[street].data'),
670 702 array(self::LEVEL_2, 'address', '[address]', 'street', '[office][street]', 'children[address].children[street].data.prop'),
  703 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data'),
  704 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data.office'),
671 705 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data.office.street'),
672 706 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data.office.street.prop'),
673 707 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data.office[street]'),
674 708 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data.office[street].prop'),
  709 + array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data[office]'),
675 710 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data[office].street'),
676 711 array(self::LEVEL_1, 'address', '[address]', 'street', '[office][street]', 'children[address].data[office].street.prop'),
677 712 array(self::LEVEL_2, 'address', '[address]', 'street', '[office][street]', 'children[address].data[office][street]'),
13 Tests/Util/PropertyPathBuilderTest.php
@@ -211,6 +211,19 @@ public function testReplaceSubstringWithLengthGreaterOne()
211 211 $this->assertEquals($path, $this->builder->getPropertyPath());
212 212 }
213 213
  214 + // https://github.com/symfony/symfony/issues/5605
  215 + public function testReplaceWithLongerPath()
  216 + {
  217 + // error occurs when path contains at least two more elements
  218 + // than the builder
  219 + $path = new PropertyPath('new1.new2.new3');
  220 +
  221 + $builder = new PropertyPathBuilder(new PropertyPath('old1'));
  222 + $builder->replace(0, 1, $path);
  223 +
  224 + $this->assertEquals($path, $builder->getPropertyPath());
  225 + }
  226 +
214 227 public function testRemove()
215 228 {
216 229 $this->builder->remove(3);
14 Util/PropertyPathBuilder.php
@@ -257,13 +257,23 @@ private function resize($offset, $cutLength, $insertionLength)
257 257 }
258 258 } else {
259 259 $diff = $insertionLength - $cutLength;
  260 +
260 261 $newLength = $length + $diff;
261 262 $indexAfterInsertion = $offset + $insertionLength;
262 263
  264 + // $diff <= $insertionLength
  265 + // $indexAfterInsertion >= $insertionLength
  266 + // => $diff <= $indexAfterInsertion
  267 +
  268 + // In each of the following loops, $i >= $diff must hold,
  269 + // otherwise ($i - $diff) becomes negative.
  270 +
263 271 // Shift old elements to the right to make up space for the
264 272 // inserted elements. This needs to be done left-to-right in
265 273 // order to preserve an ascending array index order
266   - for ($i = $length; $i < $newLength; ++$i) {
  274 + // Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff,
  275 + // $i >= $diff is guaranteed.
  276 + for ($i = max($length, $indexAfterInsertion); $i < $newLength; ++$i) {
267 277 $this->elements[$i] = $this->elements[$i - $diff];
268 278 $this->isIndex[$i] = $this->isIndex[$i - $diff];
269 279 }
@@ -273,6 +283,8 @@ private function resize($offset, $cutLength, $insertionLength)
273 283 // The last written index is the immediate index after the inserted
274 284 // string, because the indices before that will be overwritten
275 285 // anyway.
  286 + // Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff,
  287 + // $i >= $diff is guaranteed.
276 288 for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) {
277 289 $this->elements[$i] = $this->elements[$i - $diff];
278 290 $this->isIndex[$i] = $this->isIndex[$i - $diff];

0 comments on commit 404977b

Please sign in to comment.
Something went wrong with that request. Please try again.