comparison vendor/symfony/validator/Validator/RecursiveContextualValidator.php @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children 12f9dff5fda9
comparison
equal deleted inserted replaced
3:307d7a7fd348 4:a9cd425dd02b
10 */ 10 */
11 11
12 namespace Symfony\Component\Validator\Validator; 12 namespace Symfony\Component\Validator\Validator;
13 13
14 use Symfony\Component\Validator\Constraint; 14 use Symfony\Component\Validator\Constraint;
15 use Symfony\Component\Validator\Constraints\Composite;
15 use Symfony\Component\Validator\Constraints\GroupSequence; 16 use Symfony\Component\Validator\Constraints\GroupSequence;
16 use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; 17 use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
17 use Symfony\Component\Validator\Context\ExecutionContext; 18 use Symfony\Component\Validator\Context\ExecutionContext;
18 use Symfony\Component\Validator\Context\ExecutionContextInterface; 19 use Symfony\Component\Validator\Context\ExecutionContextInterface;
19 use Symfony\Component\Validator\Exception\ConstraintDefinitionException; 20 use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
21 use Symfony\Component\Validator\Exception\RuntimeException; 22 use Symfony\Component\Validator\Exception\RuntimeException;
22 use Symfony\Component\Validator\Exception\UnsupportedMetadataException; 23 use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
23 use Symfony\Component\Validator\Exception\ValidatorException; 24 use Symfony\Component\Validator\Exception\ValidatorException;
24 use Symfony\Component\Validator\Mapping\CascadingStrategy; 25 use Symfony\Component\Validator\Mapping\CascadingStrategy;
25 use Symfony\Component\Validator\Mapping\ClassMetadataInterface; 26 use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
27 use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
26 use Symfony\Component\Validator\Mapping\GenericMetadata; 28 use Symfony\Component\Validator\Mapping\GenericMetadata;
27 use Symfony\Component\Validator\Mapping\MetadataInterface; 29 use Symfony\Component\Validator\Mapping\MetadataInterface;
28 use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; 30 use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
29 use Symfony\Component\Validator\Mapping\TraversalStrategy; 31 use Symfony\Component\Validator\Mapping\TraversalStrategy;
30 use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
31 use Symfony\Component\Validator\ObjectInitializerInterface; 32 use Symfony\Component\Validator\ObjectInitializerInterface;
32 use Symfony\Component\Validator\Util\PropertyPath; 33 use Symfony\Component\Validator\Util\PropertyPath;
33 34
34 /** 35 /**
35 * Recursive implementation of {@link ContextualValidatorInterface}. 36 * Recursive implementation of {@link ContextualValidatorInterface}.
54 * of validated objects 55 * of validated objects
55 * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating 56 * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
56 * constraint validators 57 * constraint validators
57 * @param ObjectInitializerInterface[] $objectInitializers The object initializers 58 * @param ObjectInitializerInterface[] $objectInitializers The object initializers
58 */ 59 */
59 public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array()) 60 public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [])
60 { 61 {
61 $this->context = $context; 62 $this->context = $context;
62 $this->defaultPropertyPath = $context->getPropertyPath(); 63 $this->defaultPropertyPath = $context->getPropertyPath();
63 $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP); 64 $this->defaultGroups = [$context->getGroup() ?: Constraint::DEFAULT_GROUP];
64 $this->metadataFactory = $metadataFactory; 65 $this->metadataFactory = $metadataFactory;
65 $this->validatorFactory = $validatorFactory; 66 $this->validatorFactory = $validatorFactory;
66 $this->objectInitializers = $objectInitializers; 67 $this->objectInitializers = $objectInitializers;
67 } 68 }
68 69
97 // If explicit constraints are passed, validate the value against 98 // If explicit constraints are passed, validate the value against
98 // those constraints 99 // those constraints
99 if (null !== $constraints) { 100 if (null !== $constraints) {
100 // You can pass a single constraint or an array of constraints 101 // You can pass a single constraint or an array of constraints
101 // Make sure to deal with an array in the rest of the code 102 // Make sure to deal with an array in the rest of the code
102 if (!is_array($constraints)) { 103 if (!\is_array($constraints)) {
103 $constraints = array($constraints); 104 $constraints = [$constraints];
104 } 105 }
105 106
106 $metadata = new GenericMetadata(); 107 $metadata = new GenericMetadata();
107 $metadata->addConstraints($constraints); 108 $metadata->addConstraints($constraints);
108 109
109 $this->validateGenericNode( 110 $this->validateGenericNode(
110 $value, 111 $value,
111 $previousObject, 112 $previousObject,
112 is_object($value) ? spl_object_hash($value) : null, 113 \is_object($value) ? spl_object_hash($value) : null,
113 $metadata, 114 $metadata,
114 $this->defaultPropertyPath, 115 $this->defaultPropertyPath,
115 $groups, 116 $groups,
116 null, 117 null,
117 TraversalStrategy::IMPLICIT, 118 TraversalStrategy::IMPLICIT,
128 return $this; 129 return $this;
129 } 130 }
130 131
131 // If an object is passed without explicit constraints, validate that 132 // If an object is passed without explicit constraints, validate that
132 // object against the constraints defined for the object's class 133 // object against the constraints defined for the object's class
133 if (is_object($value)) { 134 if (\is_object($value)) {
134 $this->validateObject( 135 $this->validateObject(
135 $value, 136 $value,
136 $this->defaultPropertyPath, 137 $this->defaultPropertyPath,
137 $groups, 138 $groups,
138 TraversalStrategy::IMPLICIT, 139 TraversalStrategy::IMPLICIT,
145 return $this; 146 return $this;
146 } 147 }
147 148
148 // If an array is passed without explicit constraints, validate each 149 // If an array is passed without explicit constraints, validate each
149 // object in the array 150 // object in the array
150 if (is_array($value)) { 151 if (\is_array($value)) {
151 $this->validateEachObjectIn( 152 $this->validateEachObjectIn(
152 $value, 153 $value,
153 $this->defaultPropertyPath, 154 $this->defaultPropertyPath,
154 $groups, 155 $groups,
155 $this->context 156 $this->context
159 $this->context->setGroup($previousGroup); 160 $this->context->setGroup($previousGroup);
160 161
161 return $this; 162 return $this;
162 } 163 }
163 164
164 throw new RuntimeException(sprintf( 165 throw new RuntimeException(sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', \gettype($value)));
165 'Cannot validate values of type "%s" automatically. Please '.
166 'provide a constraint.',
167 gettype($value)
168 ));
169 } 166 }
170 167
171 /** 168 /**
172 * {@inheritdoc} 169 * {@inheritdoc}
173 */ 170 */
174 public function validateProperty($object, $propertyName, $groups = null) 171 public function validateProperty($object, $propertyName, $groups = null)
175 { 172 {
176 $classMetadata = $this->metadataFactory->getMetadataFor($object); 173 $classMetadata = $this->metadataFactory->getMetadataFor($object);
177 174
178 if (!$classMetadata instanceof ClassMetadataInterface) { 175 if (!$classMetadata instanceof ClassMetadataInterface) {
179 throw new ValidatorException(sprintf( 176 throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata)));
180 'The metadata factory should return instances of '.
181 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
182 'got: "%s".',
183 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
184 ));
185 } 177 }
186 178
187 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); 179 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
188 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; 180 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
189 $cacheKey = spl_object_hash($object); 181 $cacheKey = spl_object_hash($object);
199 $propertyValue = $propertyMetadata->getPropertyValue($object); 191 $propertyValue = $propertyMetadata->getPropertyValue($object);
200 192
201 $this->validateGenericNode( 193 $this->validateGenericNode(
202 $propertyValue, 194 $propertyValue,
203 $object, 195 $object,
204 $cacheKey.':'.get_class($object).':'.$propertyName, 196 $cacheKey.':'.\get_class($object).':'.$propertyName,
205 $propertyMetadata, 197 $propertyMetadata,
206 $propertyPath, 198 $propertyPath,
207 $groups, 199 $groups,
208 null, 200 null,
209 TraversalStrategy::IMPLICIT, 201 TraversalStrategy::IMPLICIT,
223 public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null) 215 public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
224 { 216 {
225 $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass); 217 $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
226 218
227 if (!$classMetadata instanceof ClassMetadataInterface) { 219 if (!$classMetadata instanceof ClassMetadataInterface) {
228 throw new ValidatorException(sprintf( 220 throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata)));
229 'The metadata factory should return instances of '.
230 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
231 'got: "%s".',
232 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
233 ));
234 } 221 }
235 222
236 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); 223 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
237 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; 224 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
238 225
239 if (is_object($objectOrClass)) { 226 if (\is_object($objectOrClass)) {
240 $object = $objectOrClass; 227 $object = $objectOrClass;
241 $class = get_class($object); 228 $class = \get_class($object);
242 $cacheKey = spl_object_hash($objectOrClass); 229 $cacheKey = spl_object_hash($objectOrClass);
243 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); 230 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
244 } else { 231 } else {
245 // $objectOrClass contains a class name 232 // $objectOrClass contains a class name
246 $object = null; 233 $object = null;
284 } 271 }
285 272
286 /** 273 /**
287 * Normalizes the given group or list of groups to an array. 274 * Normalizes the given group or list of groups to an array.
288 * 275 *
289 * @param mixed $groups The groups to normalize 276 * @param string|GroupSequence|(string|GroupSequence)[] $groups The groups to normalize
290 * 277 *
291 * @return array A group array 278 * @return (string|GroupSequence)[] A group array
292 */ 279 */
293 protected function normalizeGroups($groups) 280 protected function normalizeGroups($groups)
294 { 281 {
295 if (is_array($groups)) { 282 if (\is_array($groups)) {
296 return $groups; 283 return $groups;
297 } 284 }
298 285
299 return array($groups); 286 return [$groups];
300 } 287 }
301 288
302 /** 289 /**
303 * Validates an object against the constraints defined for its class. 290 * Validates an object against the constraints defined for its class.
304 * 291 *
307 * traversal, the object will be iterated and each nested object will be 294 * traversal, the object will be iterated and each nested object will be
308 * validated instead. 295 * validated instead.
309 * 296 *
310 * @param object $object The object to cascade 297 * @param object $object The object to cascade
311 * @param string $propertyPath The current property path 298 * @param string $propertyPath The current property path
312 * @param string[] $groups The validated groups 299 * @param (string|GroupSequence)[] $groups The validated groups
313 * @param int $traversalStrategy The strategy for traversing the 300 * @param int $traversalStrategy The strategy for traversing the
314 * cascaded object 301 * cascaded object
315 * @param ExecutionContextInterface $context The current execution context 302 * @param ExecutionContextInterface $context The current execution context
316 * 303 *
317 * @throws NoSuchMetadataException If the object has no associated metadata 304 * @throws NoSuchMetadataException If the object has no associated metadata
326 { 313 {
327 try { 314 try {
328 $classMetadata = $this->metadataFactory->getMetadataFor($object); 315 $classMetadata = $this->metadataFactory->getMetadataFor($object);
329 316
330 if (!$classMetadata instanceof ClassMetadataInterface) { 317 if (!$classMetadata instanceof ClassMetadataInterface) {
331 throw new UnsupportedMetadataException(sprintf( 318 throw new UnsupportedMetadataException(sprintf('The metadata factory should return instances of "Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata)));
332 'The metadata factory should return instances of '.
333 '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
334 'got: "%s".',
335 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
336 ));
337 } 319 }
338 320
339 $this->validateClassNode( 321 $this->validateClassNode(
340 $object, 322 $object,
341 spl_object_hash($object), 323 spl_object_hash($object),
374 * objects are iterated as well. Nested arrays are always iterated, 356 * objects are iterated as well. Nested arrays are always iterated,
375 * regardless of the value of $recursive. 357 * regardless of the value of $recursive.
376 * 358 *
377 * @param iterable $collection The collection 359 * @param iterable $collection The collection
378 * @param string $propertyPath The current property path 360 * @param string $propertyPath The current property path
379 * @param string[] $groups The validated groups 361 * @param (string|GroupSequence)[] $groups The validated groups
380 * @param ExecutionContextInterface $context The current execution context 362 * @param ExecutionContextInterface $context The current execution context
381 * 363 *
382 * @see ClassNode 364 * @see ClassNode
383 * @see CollectionNode 365 * @see CollectionNode
384 */ 366 */
385 private function validateEachObjectIn($collection, $propertyPath, array $groups, ExecutionContextInterface $context) 367 private function validateEachObjectIn($collection, $propertyPath, array $groups, ExecutionContextInterface $context)
386 { 368 {
387 foreach ($collection as $key => $value) { 369 foreach ($collection as $key => $value) {
388 if (is_array($value)) { 370 if (\is_array($value)) {
389 // Arrays are always cascaded, independent of the specified 371 // Arrays are always cascaded, independent of the specified
390 // traversal strategy 372 // traversal strategy
391 $this->validateEachObjectIn( 373 $this->validateEachObjectIn(
392 $value, 374 $value,
393 $propertyPath.'['.$key.']', 375 $propertyPath.'['.$key.']',
397 379
398 continue; 380 continue;
399 } 381 }
400 382
401 // Scalar and null values in the collection are ignored 383 // Scalar and null values in the collection are ignored
402 if (is_object($value)) { 384 if (\is_object($value)) {
403 $this->validateObject( 385 $this->validateObject(
404 $value, 386 $value,
405 $propertyPath.'['.$key.']', 387 $propertyPath.'['.$key.']',
406 $groups, 388 $groups,
407 TraversalStrategy::IMPLICIT, 389 TraversalStrategy::IMPLICIT,
441 * the validated object 423 * the validated object
442 * @param ClassMetadataInterface $metadata The class metadata of 424 * @param ClassMetadataInterface $metadata The class metadata of
443 * the object 425 * the object
444 * @param string $propertyPath The property path leading 426 * @param string $propertyPath The property path leading
445 * to the object 427 * to the object
446 * @param string[] $groups The groups in which the 428 * @param (string|GroupSequence)[] $groups The groups in which the
447 * object should be validated 429 * object should be validated
448 * @param string[]|null $cascadedGroups The groups in which 430 * @param string[]|null $cascadedGroups The groups in which
449 * cascaded objects should 431 * cascaded objects should
450 * be validated 432 * be validated
451 * @param int $traversalStrategy The strategy used for 433 * @param int $traversalStrategy The strategy used for
477 // to cascade the "Default" group when traversing the group 459 // to cascade the "Default" group when traversing the group
478 // sequence 460 // sequence
479 $defaultOverridden = false; 461 $defaultOverridden = false;
480 462
481 // Use the object hash for group sequences 463 // Use the object hash for group sequences
482 $groupHash = is_object($group) ? spl_object_hash($group) : $group; 464 $groupHash = \is_object($group) ? spl_object_hash($group) : $group;
483 465
484 if ($context->isGroupValidated($cacheKey, $groupHash)) { 466 if ($context->isGroupValidated($cacheKey, $groupHash)) {
485 // Skip this group when validating the properties and when 467 // Skip this group when validating the properties and when
486 // traversing the object 468 // traversing the object
487 unset($groups[$key]); 469 unset($groups[$key]);
542 $this->validateInGroup($object, $cacheKey, $metadata, $group, $context); 524 $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
543 } 525 }
544 526
545 // If no more groups should be validated for the property nodes, 527 // If no more groups should be validated for the property nodes,
546 // we can safely quit 528 // we can safely quit
547 if (0 === count($groups)) { 529 if (0 === \count($groups)) {
548 return; 530 return;
549 } 531 }
550 532
551 // Validate all properties against their constraints 533 // Validate all properties against their constraints
552 foreach ($metadata->getConstrainedProperties() as $propertyName) { 534 foreach ($metadata->getConstrainedProperties() as $propertyName) {
553 // If constraints are defined both on the getter of a property as 535 // If constraints are defined both on the getter of a property as
554 // well as on the property itself, then getPropertyMetadata() 536 // well as on the property itself, then getPropertyMetadata()
555 // returns two metadata objects, not just one 537 // returns two metadata objects, not just one
556 foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { 538 foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
557 if (!$propertyMetadata instanceof PropertyMetadataInterface) { 539 if (!$propertyMetadata instanceof PropertyMetadataInterface) {
558 throw new UnsupportedMetadataException(sprintf( 540 throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement "Symfony\Component\Validator\Mapping\PropertyMetadataInterface", got: "%s".', \is_object($propertyMetadata) ? \get_class($propertyMetadata) : \gettype($propertyMetadata)));
559 'The property metadata instances should implement '.
560 '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
561 'got: "%s".',
562 is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
563 ));
564 } 541 }
565 542
566 $propertyValue = $propertyMetadata->getPropertyValue($object); 543 $propertyValue = $propertyMetadata->getPropertyValue($object);
567 544
568 $this->validateGenericNode( 545 $this->validateGenericNode(
569 $propertyValue, 546 $propertyValue,
570 $object, 547 $object,
571 $cacheKey.':'.get_class($object).':'.$propertyName, 548 $cacheKey.':'.\get_class($object).':'.$propertyName,
572 $propertyMetadata, 549 $propertyMetadata,
573 PropertyPath::append($propertyPath, $propertyName), 550 PropertyPath::append($propertyPath, $propertyName),
574 $groups, 551 $groups,
575 $cascadedGroups, 552 $cascadedGroups,
576 TraversalStrategy::IMPLICIT, 553 TraversalStrategy::IMPLICIT,
595 return; 572 return;
596 } 573 }
597 574
598 // If TRAVERSE, fail if we have no Traversable 575 // If TRAVERSE, fail if we have no Traversable
599 if (!$object instanceof \Traversable) { 576 if (!$object instanceof \Traversable) {
600 throw new ConstraintDefinitionException(sprintf( 577 throw new ConstraintDefinitionException(sprintf('Traversal was enabled for "%s", but this class does not implement "\Traversable".', \get_class($object)));
601 'Traversal was enabled for "%s", but this class '.
602 'does not implement "\Traversable".',
603 get_class($object)
604 ));
605 } 578 }
606 579
607 $this->validateEachObjectIn( 580 $this->validateEachObjectIn(
608 $object, 581 $object,
609 $propertyPath, 582 $propertyPath,
634 * the validated value 607 * the validated value
635 * @param MetadataInterface $metadata The metadata of the 608 * @param MetadataInterface $metadata The metadata of the
636 * value 609 * value
637 * @param string $propertyPath The property path leading 610 * @param string $propertyPath The property path leading
638 * to the value 611 * to the value
639 * @param string[] $groups The groups in which the 612 * @param (string|GroupSequence)[] $groups The groups in which the
640 * value should be validated 613 * value should be validated
641 * @param string[]|null $cascadedGroups The groups in which 614 * @param string[]|null $cascadedGroups The groups in which
642 * cascaded objects should 615 * cascaded objects should
643 * be validated 616 * be validated
644 * @param int $traversalStrategy The strategy used for 617 * @param int $traversalStrategy The strategy used for
673 } 646 }
674 647
675 $this->validateInGroup($value, $cacheKey, $metadata, $group, $context); 648 $this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
676 } 649 }
677 650
678 if (0 === count($groups)) { 651 if (0 === \count($groups)) {
679 return; 652 return;
680 } 653 }
681 654
682 if (null === $value) { 655 if (null === $value) {
683 return; 656 return;
684 } 657 }
685 658
686 $cascadingStrategy = $metadata->getCascadingStrategy(); 659 $cascadingStrategy = $metadata->getCascadingStrategy();
687 660
688 // Quit unless we have an array or a cascaded object 661 // Quit unless we have an array or a cascaded object
689 if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) { 662 if (!\is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) {
690 return; 663 return;
691 } 664 }
692 665
693 // If no specific traversal strategy was requested when this method 666 // If no specific traversal strategy was requested when this method
694 // was called, use the traversal strategy of the node's metadata 667 // was called, use the traversal strategy of the node's metadata
697 } 670 }
698 671
699 // The $cascadedGroups property is set, if the "Default" group is 672 // The $cascadedGroups property is set, if the "Default" group is
700 // overridden by a group sequence 673 // overridden by a group sequence
701 // See validateClassNode() 674 // See validateClassNode()
702 $cascadedGroups = null !== $cascadedGroups && count($cascadedGroups) > 0 ? $cascadedGroups : $groups; 675 $cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups;
703 676
704 if (is_array($value)) { 677 if (\is_array($value)) {
705 // Arrays are always traversed, independent of the specified 678 // Arrays are always traversed, independent of the specified
706 // traversal strategy 679 // traversal strategy
707 $this->validateEachObjectIn( 680 $this->validateEachObjectIn(
708 $value, 681 $value,
709 $propertyPath, 682 $propertyPath,
755 * the group sequence 728 * the group sequence
756 * @param ExecutionContextInterface $context The execution context 729 * @param ExecutionContextInterface $context The execution context
757 */ 730 */
758 private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context) 731 private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
759 { 732 {
760 $violationCount = count($context->getViolations()); 733 $violationCount = \count($context->getViolations());
761 $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null; 734 $cascadedGroups = $cascadedGroup ? [$cascadedGroup] : null;
762 735
763 foreach ($groupSequence->groups as $groupInSequence) { 736 foreach ($groupSequence->groups as $groupInSequence) {
764 $groups = (array) $groupInSequence; 737 $groups = (array) $groupInSequence;
765 738
766 if ($metadata instanceof ClassMetadataInterface) { 739 if ($metadata instanceof ClassMetadataInterface) {
787 $context 760 $context
788 ); 761 );
789 } 762 }
790 763
791 // Abort sequence validation if a violation was generated 764 // Abort sequence validation if a violation was generated
792 if (count($context->getViolations()) > $violationCount) { 765 if (\count($context->getViolations()) > $violationCount) {
793 break; 766 break;
794 } 767 }
795 } 768 }
796 } 769 }
797 770
813 // Prevent duplicate validation of constraints, in the case 786 // Prevent duplicate validation of constraints, in the case
814 // that constraints belong to multiple validated groups 787 // that constraints belong to multiple validated groups
815 if (null !== $cacheKey) { 788 if (null !== $cacheKey) {
816 $constraintHash = spl_object_hash($constraint); 789 $constraintHash = spl_object_hash($constraint);
817 790
791 if ($constraint instanceof Composite) {
792 $constraintHash .= $group;
793 }
794
818 if ($context->isConstraintValidated($cacheKey, $constraintHash)) { 795 if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
819 continue; 796 continue;
820 } 797 }
821 798
822 $context->markConstraintAsValidated($cacheKey, $constraintHash); 799 $context->markConstraintAsValidated($cacheKey, $constraintHash);