832 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			832 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|   | <?php | ||
|  | 
 | ||
|  | /* | ||
|  |  * This file is part of the Symfony package. | ||
|  |  * | ||
|  |  * (c) Fabien Potencier <fabien@symfony.com> | ||
|  |  * | ||
|  |  * For the full copyright and license information, please view the LICENSE | ||
|  |  * file that was distributed with this source code. | ||
|  |  */ | ||
|  | 
 | ||
|  | namespace Symfony\Component\Validator\Validator; | ||
|  | 
 | ||
|  | use Symfony\Component\Validator\Constraint; | ||
|  | use Symfony\Component\Validator\Constraints\GroupSequence; | ||
|  | use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; | ||
|  | use Symfony\Component\Validator\Context\ExecutionContext; | ||
|  | use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||
|  | use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | ||
|  | use Symfony\Component\Validator\Exception\NoSuchMetadataException; | ||
|  | use Symfony\Component\Validator\Exception\RuntimeException; | ||
|  | use Symfony\Component\Validator\Exception\UnsupportedMetadataException; | ||
|  | use Symfony\Component\Validator\Exception\ValidatorException; | ||
|  | use Symfony\Component\Validator\Mapping\CascadingStrategy; | ||
|  | use Symfony\Component\Validator\Mapping\ClassMetadataInterface; | ||
|  | use Symfony\Component\Validator\Mapping\GenericMetadata; | ||
|  | use Symfony\Component\Validator\Mapping\MetadataInterface; | ||
|  | use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; | ||
|  | use Symfony\Component\Validator\Mapping\TraversalStrategy; | ||
|  | use Symfony\Component\Validator\MetadataFactoryInterface; | ||
|  | use Symfony\Component\Validator\ObjectInitializerInterface; | ||
|  | use Symfony\Component\Validator\Util\PropertyPath; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Recursive implementation of {@link ContextualValidatorInterface}. | ||
|  |  * | ||
|  |  * @author Bernhard Schussek <bschussek@gmail.com> | ||
|  |  */ | ||
|  | class RecursiveContextualValidator implements ContextualValidatorInterface | ||
|  | { | ||
|  |     private $context; | ||
|  |     private $defaultPropertyPath; | ||
|  |     private $defaultGroups; | ||
|  |     private $metadataFactory; | ||
|  |     private $validatorFactory; | ||
|  |     private $objectInitializers; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Creates a validator for the given context. | ||
|  |      * | ||
|  |      * @param ExecutionContextInterface           $context            The execution context | ||
|  |      * @param MetadataFactoryInterface            $metadataFactory    The factory for | ||
|  |      *                                                                fetching the metadata | ||
|  |      *                                                                of validated objects | ||
|  |      * @param ConstraintValidatorFactoryInterface $validatorFactory   The factory for creating | ||
|  |      *                                                                constraint validators | ||
|  |      * @param ObjectInitializerInterface[]        $objectInitializers The object initializers | ||
|  |      */ | ||
|  |     public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array()) | ||
|  |     { | ||
|  |         $this->context = $context; | ||
|  |         $this->defaultPropertyPath = $context->getPropertyPath(); | ||
|  |         $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP); | ||
|  |         $this->metadataFactory = $metadataFactory; | ||
|  |         $this->validatorFactory = $validatorFactory; | ||
|  |         $this->objectInitializers = $objectInitializers; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function atPath($path) | ||
|  |     { | ||
|  |         $this->defaultPropertyPath = $this->context->getPropertyPath($path); | ||
|  | 
 | ||
|  |         return $this; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function validate($value, $constraints = null, $groups = null) | ||
|  |     { | ||
|  |         $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; | ||
|  | 
 | ||
|  |         $previousValue = $this->context->getValue(); | ||
|  |         $previousObject = $this->context->getObject(); | ||
|  |         $previousMetadata = $this->context->getMetadata(); | ||
|  |         $previousPath = $this->context->getPropertyPath(); | ||
|  |         $previousGroup = $this->context->getGroup(); | ||
|  |         $previousConstraint = null; | ||
|  | 
 | ||
|  |         if ($this->context instanceof ExecutionContext || method_exists($this->context, 'getConstraint')) { | ||
|  |             $previousConstraint = $this->context->getConstraint(); | ||
|  |         } | ||
|  | 
 | ||
|  |         // If explicit constraints are passed, validate the value against
 | ||
|  |         // those constraints
 | ||
|  |         if (null !== $constraints) { | ||
|  |             // You can pass a single constraint or an array of constraints
 | ||
|  |             // Make sure to deal with an array in the rest of the code
 | ||
|  |             if (!\is_array($constraints)) { | ||
|  |                 $constraints = array($constraints); | ||
|  |             } | ||
|  | 
 | ||
|  |             $metadata = new GenericMetadata(); | ||
|  |             $metadata->addConstraints($constraints); | ||
|  | 
 | ||
|  |             $this->validateGenericNode( | ||
|  |                 $value, | ||
|  |                 $previousObject, | ||
|  |                 \is_object($value) ? spl_object_hash($value) : null, | ||
|  |                 $metadata, | ||
|  |                 $this->defaultPropertyPath, | ||
|  |                 $groups, | ||
|  |                 null, | ||
|  |                 TraversalStrategy::IMPLICIT, | ||
|  |                 $this->context | ||
|  |             ); | ||
|  | 
 | ||
|  |             $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); | ||
|  |             $this->context->setGroup($previousGroup); | ||
|  | 
 | ||
|  |             if (null !== $previousConstraint) { | ||
|  |                 $this->context->setConstraint($previousConstraint); | ||
|  |             } | ||
|  | 
 | ||
|  |             return $this; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If an object is passed without explicit constraints, validate that
 | ||
|  |         // object against the constraints defined for the object's class
 | ||
|  |         if (\is_object($value)) { | ||
|  |             $this->validateObject( | ||
|  |                 $value, | ||
|  |                 $this->defaultPropertyPath, | ||
|  |                 $groups, | ||
|  |                 TraversalStrategy::IMPLICIT, | ||
|  |                 $this->context | ||
|  |             ); | ||
|  | 
 | ||
|  |             $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); | ||
|  |             $this->context->setGroup($previousGroup); | ||
|  | 
 | ||
|  |             return $this; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If an array is passed without explicit constraints, validate each
 | ||
|  |         // object in the array
 | ||
|  |         if (\is_array($value)) { | ||
|  |             $this->validateEachObjectIn( | ||
|  |                 $value, | ||
|  |                 $this->defaultPropertyPath, | ||
|  |                 $groups, | ||
|  |                 true, | ||
|  |                 $this->context | ||
|  |             ); | ||
|  | 
 | ||
|  |             $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); | ||
|  |             $this->context->setGroup($previousGroup); | ||
|  | 
 | ||
|  |             return $this; | ||
|  |         } | ||
|  | 
 | ||
|  |         throw new RuntimeException(sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', \gettype($value))); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function validateProperty($object, $propertyName, $groups = null) | ||
|  |     { | ||
|  |         $classMetadata = $this->metadataFactory->getMetadataFor($object); | ||
|  | 
 | ||
|  |         if (!$classMetadata instanceof ClassMetadataInterface) { | ||
|  |             // Cannot be UnsupportedMetadataException because of BC with
 | ||
|  |             // Symfony < 2.5
 | ||
|  |             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))); | ||
|  |         } | ||
|  | 
 | ||
|  |         $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); | ||
|  |         $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; | ||
|  |         $cacheKey = spl_object_hash($object); | ||
|  |         $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); | ||
|  | 
 | ||
|  |         $previousValue = $this->context->getValue(); | ||
|  |         $previousObject = $this->context->getObject(); | ||
|  |         $previousMetadata = $this->context->getMetadata(); | ||
|  |         $previousPath = $this->context->getPropertyPath(); | ||
|  |         $previousGroup = $this->context->getGroup(); | ||
|  | 
 | ||
|  |         foreach ($propertyMetadatas as $propertyMetadata) { | ||
|  |             $propertyValue = $propertyMetadata->getPropertyValue($object); | ||
|  | 
 | ||
|  |             $this->validateGenericNode( | ||
|  |                 $propertyValue, | ||
|  |                 $object, | ||
|  |                 $cacheKey.':'.$propertyName, | ||
|  |                 $propertyMetadata, | ||
|  |                 $propertyPath, | ||
|  |                 $groups, | ||
|  |                 null, | ||
|  |                 TraversalStrategy::IMPLICIT, | ||
|  |                 $this->context | ||
|  |             ); | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); | ||
|  |         $this->context->setGroup($previousGroup); | ||
|  | 
 | ||
|  |         return $this; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null) | ||
|  |     { | ||
|  |         $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass); | ||
|  | 
 | ||
|  |         if (!$classMetadata instanceof ClassMetadataInterface) { | ||
|  |             // Cannot be UnsupportedMetadataException because of BC with
 | ||
|  |             // Symfony < 2.5
 | ||
|  |             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))); | ||
|  |         } | ||
|  | 
 | ||
|  |         $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); | ||
|  |         $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; | ||
|  | 
 | ||
|  |         if (\is_object($objectOrClass)) { | ||
|  |             $object = $objectOrClass; | ||
|  |             $cacheKey = spl_object_hash($objectOrClass); | ||
|  |             $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); | ||
|  |         } else { | ||
|  |             // $objectOrClass contains a class name
 | ||
|  |             $object = null; | ||
|  |             $cacheKey = null; | ||
|  |             $propertyPath = $this->defaultPropertyPath; | ||
|  |         } | ||
|  | 
 | ||
|  |         $previousValue = $this->context->getValue(); | ||
|  |         $previousObject = $this->context->getObject(); | ||
|  |         $previousMetadata = $this->context->getMetadata(); | ||
|  |         $previousPath = $this->context->getPropertyPath(); | ||
|  |         $previousGroup = $this->context->getGroup(); | ||
|  | 
 | ||
|  |         foreach ($propertyMetadatas as $propertyMetadata) { | ||
|  |             $this->validateGenericNode( | ||
|  |                 $value, | ||
|  |                 $object, | ||
|  |                 $cacheKey.':'.$propertyName, | ||
|  |                 $propertyMetadata, | ||
|  |                 $propertyPath, | ||
|  |                 $groups, | ||
|  |                 null, | ||
|  |                 TraversalStrategy::IMPLICIT, | ||
|  |                 $this->context | ||
|  |             ); | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); | ||
|  |         $this->context->setGroup($previousGroup); | ||
|  | 
 | ||
|  |         return $this; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function getViolations() | ||
|  |     { | ||
|  |         return $this->context->getViolations(); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Normalizes the given group or list of groups to an array. | ||
|  |      * | ||
|  |      * @param string|GroupSequence|(string|GroupSequence)[] $groups The groups to normalize | ||
|  |      * | ||
|  |      * @return (string|GroupSequence)[] A group array | ||
|  |      */ | ||
|  |     protected function normalizeGroups($groups) | ||
|  |     { | ||
|  |         if (\is_array($groups)) { | ||
|  |             return $groups; | ||
|  |         } | ||
|  | 
 | ||
|  |         return array($groups); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Validates an object against the constraints defined for its class. | ||
|  |      * | ||
|  |      * If no metadata is available for the class, but the class is an instance | ||
|  |      * of {@link \Traversable} and the selected traversal strategy allows | ||
|  |      * traversal, the object will be iterated and each nested object will be | ||
|  |      * validated instead. | ||
|  |      * | ||
|  |      * @param object                    $object            The object to cascade | ||
|  |      * @param string                    $propertyPath      The current property path | ||
|  |      * @param (string|GroupSequence)[]  $groups            The validated groups | ||
|  |      * @param int                       $traversalStrategy The strategy for traversing the | ||
|  |      *                                                     cascaded object | ||
|  |      * @param ExecutionContextInterface $context           The current execution context | ||
|  |      * | ||
|  |      * @throws NoSuchMetadataException      If the object has no associated metadata | ||
|  |      *                                      and does not implement {@link \Traversable} | ||
|  |      *                                      or if traversal is disabled via the | ||
|  |      *                                      $traversalStrategy argument | ||
|  |      * @throws UnsupportedMetadataException If the metadata returned by the | ||
|  |      *                                      metadata factory does not implement | ||
|  |      *                                      {@link ClassMetadataInterface} | ||
|  |      */ | ||
|  |     private function validateObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         try { | ||
|  |             $classMetadata = $this->metadataFactory->getMetadataFor($object); | ||
|  | 
 | ||
|  |             if (!$classMetadata instanceof ClassMetadataInterface) { | ||
|  |                 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))); | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->validateClassNode( | ||
|  |                 $object, | ||
|  |                 spl_object_hash($object), | ||
|  |                 $classMetadata, | ||
|  |                 $propertyPath, | ||
|  |                 $groups, | ||
|  |                 null, | ||
|  |                 $traversalStrategy, | ||
|  |                 $context | ||
|  |             ); | ||
|  |         } catch (NoSuchMetadataException $e) { | ||
|  |             // Rethrow if not Traversable
 | ||
|  |             if (!$object instanceof \Traversable) { | ||
|  |                 throw $e; | ||
|  |             } | ||
|  | 
 | ||
|  |             // Rethrow unless IMPLICIT or TRAVERSE
 | ||
|  |             if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) { | ||
|  |                 throw $e; | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->validateEachObjectIn( | ||
|  |                 $object, | ||
|  |                 $propertyPath, | ||
|  |                 $groups, | ||
|  |                 $traversalStrategy & TraversalStrategy::STOP_RECURSION, | ||
|  |                 $context | ||
|  |             ); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Validates each object in a collection against the constraints defined | ||
|  |      * for their classes. | ||
|  |      * | ||
|  |      * If the parameter $recursive is set to true, nested {@link \Traversable} | ||
|  |      * objects are iterated as well. Nested arrays are always iterated, | ||
|  |      * regardless of the value of $recursive. | ||
|  |      * | ||
|  |      * @param iterable                  $collection    The collection | ||
|  |      * @param string                    $propertyPath  The current property path | ||
|  |      * @param (string|GroupSequence)[]  $groups        The validated groups | ||
|  |      * @param bool                      $stopRecursion Whether to disable | ||
|  |      *                                                 recursive iteration. For | ||
|  |      *                                                 backwards compatibility | ||
|  |      *                                                 with Symfony < 2.5. | ||
|  |      * @param ExecutionContextInterface $context       The current execution context | ||
|  |      * | ||
|  |      * @see ClassNode | ||
|  |      * @see CollectionNode | ||
|  |      */ | ||
|  |     private function validateEachObjectIn($collection, $propertyPath, array $groups, $stopRecursion, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         if ($stopRecursion) { | ||
|  |             $traversalStrategy = TraversalStrategy::NONE; | ||
|  |         } else { | ||
|  |             $traversalStrategy = TraversalStrategy::IMPLICIT; | ||
|  |         } | ||
|  | 
 | ||
|  |         foreach ($collection as $key => $value) { | ||
|  |             if (\is_array($value)) { | ||
|  |                 // Arrays are always cascaded, independent of the specified
 | ||
|  |                 // traversal strategy
 | ||
|  |                 // (BC with Symfony < 2.5)
 | ||
|  |                 $this->validateEachObjectIn( | ||
|  |                     $value, | ||
|  |                     $propertyPath.'['.$key.']', | ||
|  |                     $groups, | ||
|  |                     $stopRecursion, | ||
|  |                     $context | ||
|  |                 ); | ||
|  | 
 | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             // Scalar and null values in the collection are ignored
 | ||
|  |             // (BC with Symfony < 2.5)
 | ||
|  |             if (\is_object($value)) { | ||
|  |                 $this->validateObject( | ||
|  |                     $value, | ||
|  |                     $propertyPath.'['.$key.']', | ||
|  |                     $groups, | ||
|  |                     $traversalStrategy, | ||
|  |                     $context | ||
|  |                 ); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Validates a class node. | ||
|  |      * | ||
|  |      * A class node is a combination of an object with a {@link ClassMetadataInterface} | ||
|  |      * instance. Each class node (conceptionally) has zero or more succeeding | ||
|  |      * property nodes: | ||
|  |      * | ||
|  |      *     (Article:class node) | ||
|  |      *                \ | ||
|  |      *        ($title:property node) | ||
|  |      * | ||
|  |      * This method validates the passed objects against all constraints defined | ||
|  |      * at class level. It furthermore triggers the validation of each of the | ||
|  |      * class' properties against the constraints for that property. | ||
|  |      * | ||
|  |      * If the selected traversal strategy allows traversal, the object is | ||
|  |      * iterated and each nested object is validated against its own constraints. | ||
|  |      * The object is not traversed if traversal is disabled in the class | ||
|  |      * metadata. | ||
|  |      * | ||
|  |      * If the passed groups contain the group "Default", the validator will | ||
|  |      * check whether the "Default" group has been replaced by a group sequence | ||
|  |      * in the class metadata. If this is the case, the group sequence is | ||
|  |      * validated instead. | ||
|  |      * | ||
|  |      * @param object                    $object            The validated object | ||
|  |      * @param string                    $cacheKey          The key for caching | ||
|  |      *                                                     the validated object | ||
|  |      * @param ClassMetadataInterface    $metadata          The class metadata of | ||
|  |      *                                                     the object | ||
|  |      * @param string                    $propertyPath      The property path leading | ||
|  |      *                                                     to the object | ||
|  |      * @param (string|GroupSequence)[]  $groups            The groups in which the | ||
|  |      *                                                     object should be validated | ||
|  |      * @param string[]|null             $cascadedGroups    The groups in which | ||
|  |      *                                                     cascaded objects should | ||
|  |      *                                                     be validated | ||
|  |      * @param int                       $traversalStrategy The strategy used for | ||
|  |      *                                                     traversing the object | ||
|  |      * @param ExecutionContextInterface $context           The current execution context | ||
|  |      * | ||
|  |      * @throws UnsupportedMetadataException  If a property metadata does not | ||
|  |      *                                       implement {@link PropertyMetadataInterface} | ||
|  |      * @throws ConstraintDefinitionException If traversal was enabled but the | ||
|  |      *                                       object does not implement | ||
|  |      *                                       {@link \Traversable} | ||
|  |      * | ||
|  |      * @see TraversalStrategy | ||
|  |      */ | ||
|  |     private function validateClassNode($object, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         $context->setNode($object, $object, $metadata, $propertyPath); | ||
|  | 
 | ||
|  |         if (!$context->isObjectInitialized($cacheKey)) { | ||
|  |             foreach ($this->objectInitializers as $initializer) { | ||
|  |                 $initializer->initialize($object); | ||
|  |             } | ||
|  | 
 | ||
|  |             $context->markObjectAsInitialized($cacheKey); | ||
|  |         } | ||
|  | 
 | ||
|  |         foreach ($groups as $key => $group) { | ||
|  |             // If the "Default" group is replaced by a group sequence, remember
 | ||
|  |             // to cascade the "Default" group when traversing the group
 | ||
|  |             // sequence
 | ||
|  |             $defaultOverridden = false; | ||
|  | 
 | ||
|  |             // Use the object hash for group sequences
 | ||
|  |             $groupHash = \is_object($group) ? spl_object_hash($group) : $group; | ||
|  | 
 | ||
|  |             if ($context->isGroupValidated($cacheKey, $groupHash)) { | ||
|  |                 // Skip this group when validating the properties and when
 | ||
|  |                 // traversing the object
 | ||
|  |                 unset($groups[$key]); | ||
|  | 
 | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             $context->markGroupAsValidated($cacheKey, $groupHash); | ||
|  | 
 | ||
|  |             // Replace the "Default" group by the group sequence defined
 | ||
|  |             // for the class, if applicable.
 | ||
|  |             // This is done after checking the cache, so that
 | ||
|  |             // spl_object_hash() isn't called for this sequence and
 | ||
|  |             // "Default" is used instead in the cache. This is useful
 | ||
|  |             // if the getters below return different group sequences in
 | ||
|  |             // every call.
 | ||
|  |             if (Constraint::DEFAULT_GROUP === $group) { | ||
|  |                 if ($metadata->hasGroupSequence()) { | ||
|  |                     // The group sequence is statically defined for the class
 | ||
|  |                     $group = $metadata->getGroupSequence(); | ||
|  |                     $defaultOverridden = true; | ||
|  |                 } elseif ($metadata->isGroupSequenceProvider()) { | ||
|  |                     // The group sequence is dynamically obtained from the validated
 | ||
|  |                     // object
 | ||
|  |                     /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */ | ||
|  |                     $group = $object->getGroupSequence(); | ||
|  |                     $defaultOverridden = true; | ||
|  | 
 | ||
|  |                     if (!$group instanceof GroupSequence) { | ||
|  |                         $group = new GroupSequence($group); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             // If the groups (=[<G1,G2>,G3,G4]) contain a group sequence
 | ||
|  |             // (=<G1,G2>), then call validateClassNode() with each entry of the
 | ||
|  |             // group sequence and abort if necessary (G1, G2)
 | ||
|  |             if ($group instanceof GroupSequence) { | ||
|  |                 $this->stepThroughGroupSequence( | ||
|  |                      $object, | ||
|  |                      $object, | ||
|  |                      $cacheKey, | ||
|  |                      $metadata, | ||
|  |                      $propertyPath, | ||
|  |                      $traversalStrategy, | ||
|  |                      $group, | ||
|  |                      $defaultOverridden ? Constraint::DEFAULT_GROUP : null, | ||
|  |                      $context | ||
|  |                 ); | ||
|  | 
 | ||
|  |                 // Skip the group sequence when validating properties, because
 | ||
|  |                 // stepThroughGroupSequence() already validates the properties
 | ||
|  |                 unset($groups[$key]); | ||
|  | 
 | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->validateInGroup($object, $cacheKey, $metadata, $group, $context); | ||
|  |         } | ||
|  | 
 | ||
|  |         // If no more groups should be validated for the property nodes,
 | ||
|  |         // we can safely quit
 | ||
|  |         if (0 === \count($groups)) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // Validate all properties against their constraints
 | ||
|  |         foreach ($metadata->getConstrainedProperties() as $propertyName) { | ||
|  |             // If constraints are defined both on the getter of a property as
 | ||
|  |             // well as on the property itself, then getPropertyMetadata()
 | ||
|  |             // returns two metadata objects, not just one
 | ||
|  |             foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { | ||
|  |                 if (!$propertyMetadata instanceof PropertyMetadataInterface) { | ||
|  |                     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))); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 $propertyValue = $propertyMetadata->getPropertyValue($object); | ||
|  | 
 | ||
|  |                 $this->validateGenericNode( | ||
|  |                     $propertyValue, | ||
|  |                     $object, | ||
|  |                     $cacheKey.':'.$propertyName, | ||
|  |                     $propertyMetadata, | ||
|  |                     PropertyPath::append($propertyPath, $propertyName), | ||
|  |                     $groups, | ||
|  |                     $cascadedGroups, | ||
|  |                     TraversalStrategy::IMPLICIT, | ||
|  |                     $context | ||
|  |                 ); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // If no specific traversal strategy was requested when this method
 | ||
|  |         // was called, use the traversal strategy of the class' metadata
 | ||
|  |         if ($traversalStrategy & TraversalStrategy::IMPLICIT) { | ||
|  |             // Keep the STOP_RECURSION flag, if it was set
 | ||
|  |             $traversalStrategy = $metadata->getTraversalStrategy() | ||
|  |                 | ($traversalStrategy & TraversalStrategy::STOP_RECURSION); | ||
|  |         } | ||
|  | 
 | ||
|  |         // Traverse only if IMPLICIT or TRAVERSE
 | ||
|  |         if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If IMPLICIT, stop unless we deal with a Traversable
 | ||
|  |         if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If TRAVERSE, fail if we have no Traversable
 | ||
|  |         if (!$object instanceof \Traversable) { | ||
|  |             // Must throw a ConstraintDefinitionException for backwards
 | ||
|  |             // compatibility reasons with Symfony < 2.5
 | ||
|  |             throw new ConstraintDefinitionException(sprintf('Traversal was enabled for "%s", but this class does not implement "\Traversable".', \get_class($object))); | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->validateEachObjectIn( | ||
|  |             $object, | ||
|  |             $propertyPath, | ||
|  |             $groups, | ||
|  |             $traversalStrategy & TraversalStrategy::STOP_RECURSION, | ||
|  |             $context | ||
|  |         ); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Validates a node that is not a class node. | ||
|  |      * | ||
|  |      * Currently, two such node types exist: | ||
|  |      * | ||
|  |      *  - property nodes, which consist of the value of an object's | ||
|  |      *    property together with a {@link PropertyMetadataInterface} instance | ||
|  |      *  - generic nodes, which consist of a value and some arbitrary | ||
|  |      *    constraints defined in a {@link MetadataInterface} container | ||
|  |      * | ||
|  |      * In both cases, the value is validated against all constraints defined | ||
|  |      * in the passed metadata object. Then, if the value is an instance of | ||
|  |      * {@link \Traversable} and the selected traversal strategy permits it, | ||
|  |      * the value is traversed and each nested object validated against its own | ||
|  |      * constraints. Arrays are always traversed. | ||
|  |      * | ||
|  |      * @param mixed                     $value             The validated value | ||
|  |      * @param object|null               $object            The current object | ||
|  |      * @param string                    $cacheKey          The key for caching | ||
|  |      *                                                     the validated value | ||
|  |      * @param MetadataInterface         $metadata          The metadata of the | ||
|  |      *                                                     value | ||
|  |      * @param string                    $propertyPath      The property path leading | ||
|  |      *                                                     to the value | ||
|  |      * @param (string|GroupSequence)[]  $groups            The groups in which the | ||
|  |      *                                                     value should be validated | ||
|  |      * @param string[]|null             $cascadedGroups    The groups in which | ||
|  |      *                                                     cascaded objects should | ||
|  |      *                                                     be validated | ||
|  |      * @param int                       $traversalStrategy The strategy used for | ||
|  |      *                                                     traversing the value | ||
|  |      * @param ExecutionContextInterface $context           The current execution context | ||
|  |      * | ||
|  |      * @see TraversalStrategy | ||
|  |      */ | ||
|  |     private function validateGenericNode($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         $context->setNode($value, $object, $metadata, $propertyPath); | ||
|  | 
 | ||
|  |         foreach ($groups as $key => $group) { | ||
|  |             if ($group instanceof GroupSequence) { | ||
|  |                 $this->stepThroughGroupSequence( | ||
|  |                      $value, | ||
|  |                      $object, | ||
|  |                      $cacheKey, | ||
|  |                      $metadata, | ||
|  |                      $propertyPath, | ||
|  |                      $traversalStrategy, | ||
|  |                      $group, | ||
|  |                      null, | ||
|  |                      $context | ||
|  |                 ); | ||
|  | 
 | ||
|  |                 // Skip the group sequence when cascading, as the cascading
 | ||
|  |                 // logic is already done in stepThroughGroupSequence()
 | ||
|  |                 unset($groups[$key]); | ||
|  | 
 | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->validateInGroup($value, $cacheKey, $metadata, $group, $context); | ||
|  |         } | ||
|  | 
 | ||
|  |         if (0 === \count($groups)) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (null === $value) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         $cascadingStrategy = $metadata->getCascadingStrategy(); | ||
|  | 
 | ||
|  |         // Quit unless we have an array or a cascaded object
 | ||
|  |         if (!\is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If no specific traversal strategy was requested when this method
 | ||
|  |         // was called, use the traversal strategy of the node's metadata
 | ||
|  |         if ($traversalStrategy & TraversalStrategy::IMPLICIT) { | ||
|  |             // Keep the STOP_RECURSION flag, if it was set
 | ||
|  |             $traversalStrategy = $metadata->getTraversalStrategy() | ||
|  |                 | ($traversalStrategy & TraversalStrategy::STOP_RECURSION); | ||
|  |         } | ||
|  | 
 | ||
|  |         // The $cascadedGroups property is set, if the "Default" group is
 | ||
|  |         // overridden by a group sequence
 | ||
|  |         // See validateClassNode()
 | ||
|  |         $cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups; | ||
|  | 
 | ||
|  |         if (\is_array($value)) { | ||
|  |             // Arrays are always traversed, independent of the specified
 | ||
|  |             // traversal strategy
 | ||
|  |             // (BC with Symfony < 2.5)
 | ||
|  |             $this->validateEachObjectIn( | ||
|  |                 $value, | ||
|  |                 $propertyPath, | ||
|  |                 $cascadedGroups, | ||
|  |                 $traversalStrategy & TraversalStrategy::STOP_RECURSION, | ||
|  |                 $context | ||
|  |             ); | ||
|  | 
 | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // If the value is a scalar, pass it anyway, because we want
 | ||
|  |         // a NoSuchMetadataException to be thrown in that case
 | ||
|  |         // (BC with Symfony < 2.5)
 | ||
|  |         $this->validateObject( | ||
|  |             $value, | ||
|  |             $propertyPath, | ||
|  |             $cascadedGroups, | ||
|  |             $traversalStrategy, | ||
|  |             $context | ||
|  |         ); | ||
|  | 
 | ||
|  |         // Currently, the traversal strategy can only be TRAVERSE for a
 | ||
|  |         // generic node if the cascading strategy is CASCADE. Thus, traversable
 | ||
|  |         // objects will always be handled within validateObject() and there's
 | ||
|  |         // nothing more to do here.
 | ||
|  | 
 | ||
|  |         // see GenericMetadata::addConstraint()
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Sequentially validates a node's value in each group of a group sequence. | ||
|  |      * | ||
|  |      * If any of the constraints generates a violation, subsequent groups in the | ||
|  |      * group sequence are skipped. | ||
|  |      * | ||
|  |      * @param mixed                     $value             The validated value | ||
|  |      * @param object|null               $object            The current object | ||
|  |      * @param string                    $cacheKey          The key for caching | ||
|  |      *                                                     the validated value | ||
|  |      * @param MetadataInterface         $metadata          The metadata of the | ||
|  |      *                                                     value | ||
|  |      * @param string                    $propertyPath      The property path leading | ||
|  |      *                                                     to the value | ||
|  |      * @param int                       $traversalStrategy The strategy used for | ||
|  |      *                                                     traversing the value | ||
|  |      * @param GroupSequence             $groupSequence     The group sequence | ||
|  |      * @param string|null               $cascadedGroup     The group that should | ||
|  |      *                                                     be passed to cascaded | ||
|  |      *                                                     objects instead of | ||
|  |      *                                                     the group sequence | ||
|  |      * @param ExecutionContextInterface $context           The execution context | ||
|  |      */ | ||
|  |     private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         $violationCount = \count($context->getViolations()); | ||
|  |         $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null; | ||
|  | 
 | ||
|  |         foreach ($groupSequence->groups as $groupInSequence) { | ||
|  |             $groups = array($groupInSequence); | ||
|  | 
 | ||
|  |             if ($metadata instanceof ClassMetadataInterface) { | ||
|  |                 $this->validateClassNode( | ||
|  |                      $value, | ||
|  |                      $cacheKey, | ||
|  |                      $metadata, | ||
|  |                      $propertyPath, | ||
|  |                      $groups, | ||
|  |                      $cascadedGroups, | ||
|  |                      $traversalStrategy, | ||
|  |                      $context | ||
|  |                 ); | ||
|  |             } else { | ||
|  |                 $this->validateGenericNode( | ||
|  |                      $value, | ||
|  |                      $object, | ||
|  |                      $cacheKey, | ||
|  |                      $metadata, | ||
|  |                      $propertyPath, | ||
|  |                      $groups, | ||
|  |                      $cascadedGroups, | ||
|  |                      $traversalStrategy, | ||
|  |                      $context | ||
|  |                 ); | ||
|  |             } | ||
|  | 
 | ||
|  |             // Abort sequence validation if a violation was generated
 | ||
|  |             if (\count($context->getViolations()) > $violationCount) { | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Validates a node's value against all constraints in the given group. | ||
|  |      * | ||
|  |      * @param mixed                     $value    The validated value | ||
|  |      * @param string                    $cacheKey The key for caching the | ||
|  |      *                                            validated value | ||
|  |      * @param MetadataInterface         $metadata The metadata of the value | ||
|  |      * @param string                    $group    The group to validate | ||
|  |      * @param ExecutionContextInterface $context  The execution context | ||
|  |      */ | ||
|  |     private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, $group, ExecutionContextInterface $context) | ||
|  |     { | ||
|  |         $context->setGroup($group); | ||
|  | 
 | ||
|  |         foreach ($metadata->findConstraints($group) as $constraint) { | ||
|  |             // Prevent duplicate validation of constraints, in the case
 | ||
|  |             // that constraints belong to multiple validated groups
 | ||
|  |             if (null !== $cacheKey) { | ||
|  |                 $constraintHash = spl_object_hash($constraint); | ||
|  | 
 | ||
|  |                 if ($context->isConstraintValidated($cacheKey, $constraintHash)) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 $context->markConstraintAsValidated($cacheKey, $constraintHash); | ||
|  |             } | ||
|  | 
 | ||
|  |             $context->setConstraint($constraint); | ||
|  | 
 | ||
|  |             $validator = $this->validatorFactory->getInstance($constraint); | ||
|  |             $validator->initialize($context); | ||
|  |             $validator->validate($value, $constraint); | ||
|  |         } | ||
|  |     } | ||
|  | } |