You are here

RecursiveContextualValidator.php in Plug 7

File

lib/Symfony/validator/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php
View source
<?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\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}.
 *
 * @since  2.5
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class RecursiveContextualValidator implements ContextualValidatorInterface {

  /**
   * @var ExecutionContextInterface
   */
  private $context;

  /**
   * @var MetadataFactoryInterface
   */
  private $metadataFactory;

  /**
   * @var ConstraintValidatorFactoryInterface
   */
  private $validatorFactory;

  /**
   * @var ObjectInitializerInterface[]
   */
  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();

    // 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, null, 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);
      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 mixed $groups The groups to normalize
   *
   * @return array 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[]                  $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 array|\Traversable        $collection    The collection
   * @param string                    $propertyPath  The current property path
   * @param string[]                  $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[]                  $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[]                  $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 = 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);
    }
  }

}

Classes

Namesort descending Description
RecursiveContextualValidator Recursive implementation of {@link ContextualValidatorInterface}.