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

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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\Validator\Validator;
13
14 use Symfony\Component\Validator\Constraint;
15 use Symfony\Component\Validator\Constraints\GroupSequence;
16 use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
17 use Symfony\Component\Validator\Context\ExecutionContext;
18 use Symfony\Component\Validator\Context\ExecutionContextInterface;
19 use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
20 use Symfony\Component\Validator\Exception\NoSuchMetadataException;
21 use Symfony\Component\Validator\Exception\RuntimeException;
22 use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
23 use Symfony\Component\Validator\Exception\ValidatorException;
24 use Symfony\Component\Validator\Mapping\CascadingStrategy;
25 use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
26 use Symfony\Component\Validator\Mapping\GenericMetadata;
27 use Symfony\Component\Validator\Mapping\MetadataInterface;
28 use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
29 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\Util\PropertyPath;
33
34 /**
35 * Recursive implementation of {@link ContextualValidatorInterface}.
36 *
37 * @author Bernhard Schussek <bschussek@gmail.com>
38 */
39 class RecursiveContextualValidator implements ContextualValidatorInterface
40 {
41 /**
42 * @var ExecutionContextInterface
43 */
44 private $context;
45
46 /**
47 * @var string
48 */
49 private $defaultPropertyPath;
50
51 /**
52 * @var array
53 */
54 private $defaultGroups;
55
56 /**
57 * @var MetadataFactoryInterface
58 */
59 private $metadataFactory;
60
61 /**
62 * @var ConstraintValidatorFactoryInterface
63 */
64 private $validatorFactory;
65
66 /**
67 * @var ObjectInitializerInterface[]
68 */
69 private $objectInitializers;
70
71 /**
72 * Creates a validator for the given context.
73 *
74 * @param ExecutionContextInterface $context The execution context
75 * @param MetadataFactoryInterface $metadataFactory The factory for
76 * fetching the metadata
77 * of validated objects
78 * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
79 * constraint validators
80 * @param ObjectInitializerInterface[] $objectInitializers The object initializers
81 */
82 public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
83 {
84 $this->context = $context;
85 $this->defaultPropertyPath = $context->getPropertyPath();
86 $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
87 $this->metadataFactory = $metadataFactory;
88 $this->validatorFactory = $validatorFactory;
89 $this->objectInitializers = $objectInitializers;
90 }
91
92 /**
93 * {@inheritdoc}
94 */
95 public function atPath($path)
96 {
97 $this->defaultPropertyPath = $this->context->getPropertyPath($path);
98
99 return $this;
100 }
101
102 /**
103 * {@inheritdoc}
104 */
105 public function validate($value, $constraints = null, $groups = null)
106 {
107 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
108
109 $previousValue = $this->context->getValue();
110 $previousObject = $this->context->getObject();
111 $previousMetadata = $this->context->getMetadata();
112 $previousPath = $this->context->getPropertyPath();
113 $previousGroup = $this->context->getGroup();
114 $previousConstraint = null;
115
116 if ($this->context instanceof ExecutionContext || method_exists($this->context, 'getConstraint')) {
117 $previousConstraint = $this->context->getConstraint();
118 }
119
120 // If explicit constraints are passed, validate the value against
121 // those constraints
122 if (null !== $constraints) {
123 // You can pass a single constraint or an array of constraints
124 // Make sure to deal with an array in the rest of the code
125 if (!is_array($constraints)) {
126 $constraints = array($constraints);
127 }
128
129 $metadata = new GenericMetadata();
130 $metadata->addConstraints($constraints);
131
132 $this->validateGenericNode(
133 $value,
134 null,
135 is_object($value) ? spl_object_hash($value) : null,
136 $metadata,
137 $this->defaultPropertyPath,
138 $groups,
139 null,
140 TraversalStrategy::IMPLICIT,
141 $this->context
142 );
143
144 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
145 $this->context->setGroup($previousGroup);
146
147 if (null !== $previousConstraint) {
148 $this->context->setConstraint($previousConstraint);
149 }
150
151 return $this;
152 }
153
154 // If an object is passed without explicit constraints, validate that
155 // object against the constraints defined for the object's class
156 if (is_object($value)) {
157 $this->validateObject(
158 $value,
159 $this->defaultPropertyPath,
160 $groups,
161 TraversalStrategy::IMPLICIT,
162 $this->context
163 );
164
165 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
166 $this->context->setGroup($previousGroup);
167
168 return $this;
169 }
170
171 // If an array is passed without explicit constraints, validate each
172 // object in the array
173 if (is_array($value)) {
174 $this->validateEachObjectIn(
175 $value,
176 $this->defaultPropertyPath,
177 $groups,
178 $this->context
179 );
180
181 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
182 $this->context->setGroup($previousGroup);
183
184 return $this;
185 }
186
187 throw new RuntimeException(sprintf(
188 'Cannot validate values of type "%s" automatically. Please '.
189 'provide a constraint.',
190 gettype($value)
191 ));
192 }
193
194 /**
195 * {@inheritdoc}
196 */
197 public function validateProperty($object, $propertyName, $groups = null)
198 {
199 $classMetadata = $this->metadataFactory->getMetadataFor($object);
200
201 if (!$classMetadata instanceof ClassMetadataInterface) {
202 throw new ValidatorException(sprintf(
203 'The metadata factory should return instances of '.
204 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
205 'got: "%s".',
206 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
207 ));
208 }
209
210 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
211 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
212 $cacheKey = spl_object_hash($object);
213 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
214
215 $previousValue = $this->context->getValue();
216 $previousObject = $this->context->getObject();
217 $previousMetadata = $this->context->getMetadata();
218 $previousPath = $this->context->getPropertyPath();
219 $previousGroup = $this->context->getGroup();
220
221 foreach ($propertyMetadatas as $propertyMetadata) {
222 $propertyValue = $propertyMetadata->getPropertyValue($object);
223
224 $this->validateGenericNode(
225 $propertyValue,
226 $object,
227 $cacheKey.':'.get_class($object).':'.$propertyName,
228 $propertyMetadata,
229 $propertyPath,
230 $groups,
231 null,
232 TraversalStrategy::IMPLICIT,
233 $this->context
234 );
235 }
236
237 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
238 $this->context->setGroup($previousGroup);
239
240 return $this;
241 }
242
243 /**
244 * {@inheritdoc}
245 */
246 public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
247 {
248 $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
249
250 if (!$classMetadata instanceof ClassMetadataInterface) {
251 throw new ValidatorException(sprintf(
252 'The metadata factory should return instances of '.
253 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
254 'got: "%s".',
255 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
256 ));
257 }
258
259 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
260 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
261
262 if (is_object($objectOrClass)) {
263 $object = $objectOrClass;
264 $cacheKey = spl_object_hash($objectOrClass);
265 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
266 } else {
267 // $objectOrClass contains a class name
268 $object = null;
269 $cacheKey = null;
270 $propertyPath = $this->defaultPropertyPath;
271 }
272
273 $previousValue = $this->context->getValue();
274 $previousObject = $this->context->getObject();
275 $previousMetadata = $this->context->getMetadata();
276 $previousPath = $this->context->getPropertyPath();
277 $previousGroup = $this->context->getGroup();
278
279 foreach ($propertyMetadatas as $propertyMetadata) {
280 $this->validateGenericNode(
281 $value,
282 $object,
283 $cacheKey.':'.get_class($object).':'.$propertyName,
284 $propertyMetadata,
285 $propertyPath,
286 $groups,
287 null,
288 TraversalStrategy::IMPLICIT,
289 $this->context
290 );
291 }
292
293 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
294 $this->context->setGroup($previousGroup);
295
296 return $this;
297 }
298
299 /**
300 * {@inheritdoc}
301 */
302 public function getViolations()
303 {
304 return $this->context->getViolations();
305 }
306
307 /**
308 * Normalizes the given group or list of groups to an array.
309 *
310 * @param mixed $groups The groups to normalize
311 *
312 * @return array A group array
313 */
314 protected function normalizeGroups($groups)
315 {
316 if (is_array($groups)) {
317 return $groups;
318 }
319
320 return array($groups);
321 }
322
323 /**
324 * Validates an object against the constraints defined for its class.
325 *
326 * If no metadata is available for the class, but the class is an instance
327 * of {@link \Traversable} and the selected traversal strategy allows
328 * traversal, the object will be iterated and each nested object will be
329 * validated instead.
330 *
331 * @param object $object The object to cascade
332 * @param string $propertyPath The current property path
333 * @param string[] $groups The validated groups
334 * @param int $traversalStrategy The strategy for traversing the
335 * cascaded object
336 * @param ExecutionContextInterface $context The current execution context
337 *
338 * @throws NoSuchMetadataException If the object has no associated metadata
339 * and does not implement {@link \Traversable}
340 * or if traversal is disabled via the
341 * $traversalStrategy argument
342 * @throws UnsupportedMetadataException If the metadata returned by the
343 * metadata factory does not implement
344 * {@link ClassMetadataInterface}
345 */
346 private function validateObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
347 {
348 try {
349 $classMetadata = $this->metadataFactory->getMetadataFor($object);
350
351 if (!$classMetadata instanceof ClassMetadataInterface) {
352 throw new UnsupportedMetadataException(sprintf(
353 'The metadata factory should return instances of '.
354 '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
355 'got: "%s".',
356 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
357 ));
358 }
359
360 $this->validateClassNode(
361 $object,
362 spl_object_hash($object),
363 $classMetadata,
364 $propertyPath,
365 $groups,
366 null,
367 $traversalStrategy,
368 $context
369 );
370 } catch (NoSuchMetadataException $e) {
371 // Rethrow if not Traversable
372 if (!$object instanceof \Traversable) {
373 throw $e;
374 }
375
376 // Rethrow unless IMPLICIT or TRAVERSE
377 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
378 throw $e;
379 }
380
381 $this->validateEachObjectIn(
382 $object,
383 $propertyPath,
384 $groups,
385 $context
386 );
387 }
388 }
389
390 /**
391 * Validates each object in a collection against the constraints defined
392 * for their classes.
393 *
394 * If the parameter $recursive is set to true, nested {@link \Traversable}
395 * objects are iterated as well. Nested arrays are always iterated,
396 * regardless of the value of $recursive.
397 *
398 * @param array|\Traversable $collection The collection
399 * @param string $propertyPath The current property path
400 * @param string[] $groups The validated groups
401 * @param ExecutionContextInterface $context The current execution context
402 *
403 * @see ClassNode
404 * @see CollectionNode
405 */
406 private function validateEachObjectIn($collection, $propertyPath, array $groups, ExecutionContextInterface $context)
407 {
408 foreach ($collection as $key => $value) {
409 if (is_array($value)) {
410 // Arrays are always cascaded, independent of the specified
411 // traversal strategy
412 $this->validateEachObjectIn(
413 $value,
414 $propertyPath.'['.$key.']',
415 $groups,
416 $context
417 );
418
419 continue;
420 }
421
422 // Scalar and null values in the collection are ignored
423 if (is_object($value)) {
424 $this->validateObject(
425 $value,
426 $propertyPath.'['.$key.']',
427 $groups,
428 TraversalStrategy::IMPLICIT,
429 $context
430 );
431 }
432 }
433 }
434
435 /**
436 * Validates a class node.
437 *
438 * A class node is a combination of an object with a {@link ClassMetadataInterface}
439 * instance. Each class node (conceptionally) has zero or more succeeding
440 * property nodes:
441 *
442 * (Article:class node)
443 * \
444 * ($title:property node)
445 *
446 * This method validates the passed objects against all constraints defined
447 * at class level. It furthermore triggers the validation of each of the
448 * class' properties against the constraints for that property.
449 *
450 * If the selected traversal strategy allows traversal, the object is
451 * iterated and each nested object is validated against its own constraints.
452 * The object is not traversed if traversal is disabled in the class
453 * metadata.
454 *
455 * If the passed groups contain the group "Default", the validator will
456 * check whether the "Default" group has been replaced by a group sequence
457 * in the class metadata. If this is the case, the group sequence is
458 * validated instead.
459 *
460 * @param object $object The validated object
461 * @param string $cacheKey The key for caching
462 * the validated object
463 * @param ClassMetadataInterface $metadata The class metadata of
464 * the object
465 * @param string $propertyPath The property path leading
466 * to the object
467 * @param string[] $groups The groups in which the
468 * object should be validated
469 * @param string[]|null $cascadedGroups The groups in which
470 * cascaded objects should
471 * be validated
472 * @param int $traversalStrategy The strategy used for
473 * traversing the object
474 * @param ExecutionContextInterface $context The current execution context
475 *
476 * @throws UnsupportedMetadataException If a property metadata does not
477 * implement {@link PropertyMetadataInterface}
478 * @throws ConstraintDefinitionException If traversal was enabled but the
479 * object does not implement
480 * {@link \Traversable}
481 *
482 * @see TraversalStrategy
483 */
484 private function validateClassNode($object, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
485 {
486 $context->setNode($object, $object, $metadata, $propertyPath);
487
488 if (!$context->isObjectInitialized($cacheKey)) {
489 foreach ($this->objectInitializers as $initializer) {
490 $initializer->initialize($object);
491 }
492
493 $context->markObjectAsInitialized($cacheKey);
494 }
495
496 foreach ($groups as $key => $group) {
497 // If the "Default" group is replaced by a group sequence, remember
498 // to cascade the "Default" group when traversing the group
499 // sequence
500 $defaultOverridden = false;
501
502 // Use the object hash for group sequences
503 $groupHash = is_object($group) ? spl_object_hash($group) : $group;
504
505 if ($context->isGroupValidated($cacheKey, $groupHash)) {
506 // Skip this group when validating the properties and when
507 // traversing the object
508 unset($groups[$key]);
509
510 continue;
511 }
512
513 $context->markGroupAsValidated($cacheKey, $groupHash);
514
515 // Replace the "Default" group by the group sequence defined
516 // for the class, if applicable.
517 // This is done after checking the cache, so that
518 // spl_object_hash() isn't called for this sequence and
519 // "Default" is used instead in the cache. This is useful
520 // if the getters below return different group sequences in
521 // every call.
522 if (Constraint::DEFAULT_GROUP === $group) {
523 if ($metadata->hasGroupSequence()) {
524 // The group sequence is statically defined for the class
525 $group = $metadata->getGroupSequence();
526 $defaultOverridden = true;
527 } elseif ($metadata->isGroupSequenceProvider()) {
528 // The group sequence is dynamically obtained from the validated
529 // object
530 /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
531 $group = $object->getGroupSequence();
532 $defaultOverridden = true;
533
534 if (!$group instanceof GroupSequence) {
535 $group = new GroupSequence($group);
536 }
537 }
538 }
539
540 // If the groups (=[<G1,G2>,G3,G4]) contain a group sequence
541 // (=<G1,G2>), then call validateClassNode() with each entry of the
542 // group sequence and abort if necessary (G1, G2)
543 if ($group instanceof GroupSequence) {
544 $this->stepThroughGroupSequence(
545 $object,
546 $object,
547 $cacheKey,
548 $metadata,
549 $propertyPath,
550 $traversalStrategy,
551 $group,
552 $defaultOverridden ? Constraint::DEFAULT_GROUP : null,
553 $context
554 );
555
556 // Skip the group sequence when validating properties, because
557 // stepThroughGroupSequence() already validates the properties
558 unset($groups[$key]);
559
560 continue;
561 }
562
563 $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
564 }
565
566 // If no more groups should be validated for the property nodes,
567 // we can safely quit
568 if (0 === count($groups)) {
569 return;
570 }
571
572 // Validate all properties against their constraints
573 foreach ($metadata->getConstrainedProperties() as $propertyName) {
574 // If constraints are defined both on the getter of a property as
575 // well as on the property itself, then getPropertyMetadata()
576 // returns two metadata objects, not just one
577 foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
578 if (!$propertyMetadata instanceof PropertyMetadataInterface) {
579 throw new UnsupportedMetadataException(sprintf(
580 'The property metadata instances should implement '.
581 '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
582 'got: "%s".',
583 is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
584 ));
585 }
586
587 $propertyValue = $propertyMetadata->getPropertyValue($object);
588
589 $this->validateGenericNode(
590 $propertyValue,
591 $object,
592 $cacheKey.':'.get_class($object).':'.$propertyName,
593 $propertyMetadata,
594 PropertyPath::append($propertyPath, $propertyName),
595 $groups,
596 $cascadedGroups,
597 TraversalStrategy::IMPLICIT,
598 $context
599 );
600 }
601 }
602
603 // If no specific traversal strategy was requested when this method
604 // was called, use the traversal strategy of the class' metadata
605 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
606 $traversalStrategy = $metadata->getTraversalStrategy();
607 }
608
609 // Traverse only if IMPLICIT or TRAVERSE
610 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
611 return;
612 }
613
614 // If IMPLICIT, stop unless we deal with a Traversable
615 if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) {
616 return;
617 }
618
619 // If TRAVERSE, fail if we have no Traversable
620 if (!$object instanceof \Traversable) {
621 throw new ConstraintDefinitionException(sprintf(
622 'Traversal was enabled for "%s", but this class '.
623 'does not implement "\Traversable".',
624 get_class($object)
625 ));
626 }
627
628 $this->validateEachObjectIn(
629 $object,
630 $propertyPath,
631 $groups,
632 $context
633 );
634 }
635
636 /**
637 * Validates a node that is not a class node.
638 *
639 * Currently, two such node types exist:
640 *
641 * - property nodes, which consist of the value of an object's
642 * property together with a {@link PropertyMetadataInterface} instance
643 * - generic nodes, which consist of a value and some arbitrary
644 * constraints defined in a {@link MetadataInterface} container
645 *
646 * In both cases, the value is validated against all constraints defined
647 * in the passed metadata object. Then, if the value is an instance of
648 * {@link \Traversable} and the selected traversal strategy permits it,
649 * the value is traversed and each nested object validated against its own
650 * constraints. Arrays are always traversed.
651 *
652 * @param mixed $value The validated value
653 * @param object|null $object The current object
654 * @param string $cacheKey The key for caching
655 * the validated value
656 * @param MetadataInterface $metadata The metadata of the
657 * value
658 * @param string $propertyPath The property path leading
659 * to the value
660 * @param string[] $groups The groups in which the
661 * value should be validated
662 * @param string[]|null $cascadedGroups The groups in which
663 * cascaded objects should
664 * be validated
665 * @param int $traversalStrategy The strategy used for
666 * traversing the value
667 * @param ExecutionContextInterface $context The current execution context
668 *
669 * @see TraversalStrategy
670 */
671 private function validateGenericNode($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
672 {
673 $context->setNode($value, $object, $metadata, $propertyPath);
674
675 foreach ($groups as $key => $group) {
676 if ($group instanceof GroupSequence) {
677 $this->stepThroughGroupSequence(
678 $value,
679 $object,
680 $cacheKey,
681 $metadata,
682 $propertyPath,
683 $traversalStrategy,
684 $group,
685 null,
686 $context
687 );
688
689 // Skip the group sequence when cascading, as the cascading
690 // logic is already done in stepThroughGroupSequence()
691 unset($groups[$key]);
692
693 continue;
694 }
695
696 $this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
697 }
698
699 if (0 === count($groups)) {
700 return;
701 }
702
703 if (null === $value) {
704 return;
705 }
706
707 $cascadingStrategy = $metadata->getCascadingStrategy();
708
709 // Quit unless we have an array or a cascaded object
710 if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) {
711 return;
712 }
713
714 // If no specific traversal strategy was requested when this method
715 // was called, use the traversal strategy of the node's metadata
716 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
717 $traversalStrategy = $metadata->getTraversalStrategy();
718 }
719
720 // The $cascadedGroups property is set, if the "Default" group is
721 // overridden by a group sequence
722 // See validateClassNode()
723 $cascadedGroups = null !== $cascadedGroups && count($cascadedGroups) > 0 ? $cascadedGroups : $groups;
724
725 if (is_array($value)) {
726 // Arrays are always traversed, independent of the specified
727 // traversal strategy
728 $this->validateEachObjectIn(
729 $value,
730 $propertyPath,
731 $cascadedGroups,
732 $context
733 );
734
735 return;
736 }
737
738 // If the value is a scalar, pass it anyway, because we want
739 // a NoSuchMetadataException to be thrown in that case
740 $this->validateObject(
741 $value,
742 $propertyPath,
743 $cascadedGroups,
744 $traversalStrategy,
745 $context
746 );
747
748 // Currently, the traversal strategy can only be TRAVERSE for a
749 // generic node if the cascading strategy is CASCADE. Thus, traversable
750 // objects will always be handled within validateObject() and there's
751 // nothing more to do here.
752
753 // see GenericMetadata::addConstraint()
754 }
755
756 /**
757 * Sequentially validates a node's value in each group of a group sequence.
758 *
759 * If any of the constraints generates a violation, subsequent groups in the
760 * group sequence are skipped.
761 *
762 * @param mixed $value The validated value
763 * @param object|null $object The current object
764 * @param string $cacheKey The key for caching
765 * the validated value
766 * @param MetadataInterface $metadata The metadata of the
767 * value
768 * @param string $propertyPath The property path leading
769 * to the value
770 * @param int $traversalStrategy The strategy used for
771 * traversing the value
772 * @param GroupSequence $groupSequence The group sequence
773 * @param string|null $cascadedGroup The group that should
774 * be passed to cascaded
775 * objects instead of
776 * the group sequence
777 * @param ExecutionContextInterface $context The execution context
778 */
779 private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
780 {
781 $violationCount = count($context->getViolations());
782 $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
783
784 foreach ($groupSequence->groups as $groupInSequence) {
785 $groups = (array) $groupInSequence;
786
787 if ($metadata instanceof ClassMetadataInterface) {
788 $this->validateClassNode(
789 $value,
790 $cacheKey,
791 $metadata,
792 $propertyPath,
793 $groups,
794 $cascadedGroups,
795 $traversalStrategy,
796 $context
797 );
798 } else {
799 $this->validateGenericNode(
800 $value,
801 $object,
802 $cacheKey,
803 $metadata,
804 $propertyPath,
805 $groups,
806 $cascadedGroups,
807 $traversalStrategy,
808 $context
809 );
810 }
811
812 // Abort sequence validation if a violation was generated
813 if (count($context->getViolations()) > $violationCount) {
814 break;
815 }
816 }
817 }
818
819 /**
820 * Validates a node's value against all constraints in the given group.
821 *
822 * @param mixed $value The validated value
823 * @param string $cacheKey The key for caching the
824 * validated value
825 * @param MetadataInterface $metadata The metadata of the value
826 * @param string $group The group to validate
827 * @param ExecutionContextInterface $context The execution context
828 */
829 private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, $group, ExecutionContextInterface $context)
830 {
831 $context->setGroup($group);
832
833 foreach ($metadata->findConstraints($group) as $constraint) {
834 // Prevent duplicate validation of constraints, in the case
835 // that constraints belong to multiple validated groups
836 if (null !== $cacheKey) {
837 $constraintHash = spl_object_hash($constraint);
838
839 if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
840 continue;
841 }
842
843 $context->markConstraintAsValidated($cacheKey, $constraintHash);
844 }
845
846 $context->setConstraint($constraint);
847
848 $validator = $this->validatorFactory->getInstance($constraint);
849 $validator->initialize($context);
850 $validator->validate($value, $constraint);
851 }
852 }
853 }