You are here

class RecursiveContextualValidator in Drupal 8

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php \Drupal\Core\TypedData\Validation\RecursiveContextualValidator

Defines a recursive contextual validator for Typed Data.

For both list and complex data it call recursively out to the properties / elements of the list.

This class calls out to some methods on the execution context marked as internal. These methods are internal to the validator (which is implemented by this class) but should not be called by users. See http://symfony.com/doc/current/contributing/code/bc.html for more information about @internal.

Hierarchy

Expanded class hierarchy of RecursiveContextualValidator

See also

\Drupal\Core\TypedData\Validation\RecursiveValidator::startContext()

\Drupal\Core\TypedData\Validation\RecursiveValidator::inContext()

File

core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php, line 31

Namespace

Drupal\Core\TypedData\Validation
View source
class RecursiveContextualValidator implements ContextualValidatorInterface {

  /**
   * The execution context.
   *
   * @var \Symfony\Component\Validator\Context\ExecutionContextInterface
   */
  protected $context;

  /**
   * The metadata factory.
   *
   * @var \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface
   */
  protected $metadataFactory;

  /**
   * The constraint validator factory.
   *
   * @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
   */
  protected $constraintValidatorFactory;

  /**
   * Creates a validator for the given context.
   *
   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
   *   The factory for creating new contexts.
   * @param \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface $metadata_factory
   *   The metadata factory.
   * @param \Symfony\Component\Validator\ConstraintValidatorFactoryInterface $validator_factory
   *   The constraint validator factory.
   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
   *   The typed data manager.
   */
  public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadata_factory, ConstraintValidatorFactoryInterface $validator_factory, TypedDataManagerInterface $typed_data_manager) {
    $this->context = $context;
    $this->metadataFactory = $metadata_factory;
    $this->constraintValidatorFactory = $validator_factory;
    $this->typedDataManager = $typed_data_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function atPath($path) {

    // @todo This method is not used at the moment, see
    //   https://www.drupal.org/node/2482527
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function validate($data, $constraints = NULL, $groups = NULL, $is_root_call = TRUE) {
    if (isset($groups)) {
      throw new \LogicException('Passing custom groups is not supported.');
    }
    if (!$data instanceof TypedDataInterface) {
      throw new \InvalidArgumentException('The passed value must be a typed data object.');
    }

    // 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 (isset($constraints) && !is_array($constraints)) {
      $constraints = [
        $constraints,
      ];
    }
    $this
      ->validateNode($data, $constraints, $is_root_call);
    return $this;
  }

  /**
   * Validates a Typed Data node in the validation tree.
   *
   * If no constraints are passed, the data is validated against the
   * constraints specified in its data definition. If the data is complex or a
   * list and no constraints are passed, the contained properties or list items
   * are validated recursively.
   *
   * @param \Drupal\Core\TypedData\TypedDataInterface $data
   *   The data to validated.
   * @param \Symfony\Component\Validator\Constraint[]|null $constraints
   *   (optional) If set, an array of constraints to validate.
   * @param bool $is_root_call
   *   (optional) Whether its the most upper call in the type data tree.
   *
   * @return $this
   */
  protected function validateNode(TypedDataInterface $data, $constraints = NULL, $is_root_call = FALSE) {
    $previous_value = $this->context
      ->getValue();
    $previous_object = $this->context
      ->getObject();
    $previous_metadata = $this->context
      ->getMetadata();
    $previous_path = $this->context
      ->getPropertyPath();
    $metadata = $this->metadataFactory
      ->getMetadataFor($data);
    $cache_key = spl_object_hash($data);
    $property_path = $is_root_call ? '' : PropertyPath::append($previous_path, $data
      ->getName());

    // Prefer a specific instance of the typed data manager stored by the data
    // if it is available. This is necessary for specialized typed data objects,
    // for example those using the typed config subclass of the manager.
    $typed_data_manager = method_exists($data, 'getTypedDataManager') ? $data
      ->getTypedDataManager() : $this->typedDataManager;

    // Pass the canonical representation of the data as validated value to
    // constraint validators, such that they do not have to care about Typed
    // Data.
    $value = $typed_data_manager
      ->getCanonicalRepresentation($data);
    $constraints_given = isset($constraints);
    $this->context
      ->setNode($value, $data, $metadata, $property_path);
    if (isset($constraints) || !$this->context
      ->isGroupValidated($cache_key, Constraint::DEFAULT_GROUP)) {
      if (!isset($constraints)) {
        $this->context
          ->markGroupAsValidated($cache_key, Constraint::DEFAULT_GROUP);
        $constraints = $metadata
          ->findConstraints(Constraint::DEFAULT_GROUP);
      }
      $this
        ->validateConstraints($value, $cache_key, $constraints);
    }

    // If the data is a list or complex data, validate the contained list items
    // or properties. However, do not recurse if the data is empty.
    // Next, we do not recurse if given constraints are validated against an
    // entity, since we should determine whether the entity matches the
    // constraints and not whether the entity validates.
    if (($data instanceof ListInterface || $data instanceof ComplexDataInterface) && !$data
      ->isEmpty() && !($data instanceof EntityAdapter && $constraints_given)) {
      foreach ($data as $name => $property) {
        $this
          ->validateNode($property);
      }
    }
    $this->context
      ->setNode($previous_value, $previous_object, $previous_metadata, $previous_path);
    return $this;
  }

  /**
   * Validates a node's value against all constraints in the given group.
   *
   * @param mixed $value
   *   The validated value.
   * @param string $cache_key
   *   The cache key used internally to ensure we don't validate the same
   *   constraint twice.
   * @param \Symfony\Component\Validator\Constraint[] $constraints
   *   The constraints which should be ensured for the given value.
   */
  protected function validateConstraints($value, $cache_key, $constraints) {
    foreach ($constraints as $constraint) {

      // Prevent duplicate validation of constraints, in the case
      // that constraints belong to multiple validated groups
      if (isset($cache_key)) {
        $constraint_hash = spl_object_hash($constraint);
        if ($this->context
          ->isConstraintValidated($cache_key, $constraint_hash)) {
          continue;
        }
        $this->context
          ->markConstraintAsValidated($cache_key, $constraint_hash);
      }
      $this->context
        ->setConstraint($constraint);
      $validator = $this->constraintValidatorFactory
        ->getInstance($constraint);
      $validator
        ->initialize($this->context);
      $validator
        ->validate($value, $constraint);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getViolations() {
    return $this->context
      ->getViolations();
  }

  /**
   * {@inheritdoc}
   */
  public function validateProperty($object, $propertyName, $groups = NULL) {
    if (isset($groups)) {
      throw new \LogicException('Passing custom groups is not supported.');
    }
    if (!is_object($object)) {
      throw new \InvalidArgumentException('Passing class name is not supported.');
    }
    elseif (!$object instanceof TypedDataInterface) {
      throw new \InvalidArgumentException('The passed in object has to be typed data.');
    }
    elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) {
      throw new \InvalidArgumentException('Passed data does not contain properties.');
    }
    return $this
      ->validateNode($object
      ->get($propertyName), NULL, TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function validatePropertyValue($object, $property_name, $value, $groups = NULL) {
    if (!is_object($object)) {
      throw new \InvalidArgumentException('Passing class name is not supported.');
    }
    elseif (!$object instanceof TypedDataInterface) {
      throw new \InvalidArgumentException('The passed in object has to be typed data.');
    }
    elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) {
      throw new \InvalidArgumentException('Passed data does not contain properties.');
    }
    $data = $object
      ->get($property_name);
    $metadata = $this->metadataFactory
      ->getMetadataFor($data);
    $constraints = $metadata
      ->findConstraints(Constraint::DEFAULT_GROUP);
    return $this
      ->validate($value, $constraints, $groups, TRUE);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RecursiveContextualValidator::$constraintValidatorFactory protected property The constraint validator factory.
RecursiveContextualValidator::$context protected property The execution context.
RecursiveContextualValidator::$metadataFactory protected property The metadata factory.
RecursiveContextualValidator::atPath public function Appends the given path to the property path of the context.
RecursiveContextualValidator::getViolations public function Returns the violations that have been generated so far in the context of the validator.
RecursiveContextualValidator::validate public function Validates a value against a constraint or a list of constraints. Overrides ContextualValidatorInterface::validate
RecursiveContextualValidator::validateConstraints protected function Validates a node's value against all constraints in the given group.
RecursiveContextualValidator::validateNode protected function Validates a Typed Data node in the validation tree.
RecursiveContextualValidator::validateProperty public function Validates a property of an object against the constraints specified for this property.
RecursiveContextualValidator::validatePropertyValue public function Validates a value against the constraints specified for an object's property.
RecursiveContextualValidator::__construct public function Creates a validator for the given context.