1<?php
2
3namespace Drupal\Core\TypedData\Validation;
4
5use Drupal\Core\TypedData\TypedDataInterface;
6use Drupal\Core\TypedData\TypedDataManagerInterface;
7use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
8use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
9use Symfony\Component\Validator\Context\ExecutionContextInterface;
10use Symfony\Component\Validator\Validator\ValidatorInterface;
11
12/**
13 * Defines a recursive validator for Typed Data.
14 *
15 * The difference to \Symfony\Component\Validator\Validator\RecursiveValidator
16 * is that we just allow to validate typed data objects.
17 */
18class RecursiveValidator implements ValidatorInterface {
19
20  /**
21   * @var \Symfony\Component\Validator\Context\ExecutionContextFactoryInterface
22   */
23  protected $contextFactory;
24
25  /**
26   * @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
27   */
28  protected $constraintValidatorFactory;
29
30  /**
31   * @var \Drupal\Core\TypedData\TypedDataManager
32   */
33  protected $typedDataManager;
34
35  /**
36   * Creates a new validator.
37   *
38   * @param \Symfony\Component\Validator\Context\ExecutionContextFactoryInterface $context_factory
39   *   The factory for creating new contexts.
40   * @param \Symfony\Component\Validator\ConstraintValidatorFactoryInterface $validator_factory
41   *   The constraint validator factory.
42   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
43   *   The typed data manager.
44   */
45  public function __construct(ExecutionContextFactoryInterface $context_factory, ConstraintValidatorFactoryInterface $validator_factory, TypedDataManagerInterface $typed_data_manager) {
46    $this->contextFactory = $context_factory;
47    $this->constraintValidatorFactory = $validator_factory;
48    $this->typedDataManager = $typed_data_manager;
49  }
50
51  /**
52   * {@inheritdoc}
53   */
54  public function startContext($root = NULL) {
55    return new RecursiveContextualValidator($this->contextFactory->createContext($this, $root), $this, $this->constraintValidatorFactory, $this->typedDataManager);
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function inContext(ExecutionContextInterface $context) {
62    return new RecursiveContextualValidator($context, $this, $this->constraintValidatorFactory, $this->typedDataManager);
63  }
64
65  /**
66   * {@inheritdoc}
67   *
68   * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
69   *   A typed data object containing the value to validate.
70   */
71  public function getMetadataFor($typed_data) {
72    if (!$typed_data instanceof TypedDataInterface) {
73      throw new \InvalidArgumentException('The passed value must be a typed data object.');
74    }
75    return new TypedDataMetadata($typed_data);
76  }
77
78  /**
79   * {@inheritdoc}
80   */
81  public function hasMetadataFor($value) {
82    return $value instanceof TypedDataInterface;
83  }
84
85  /**
86   * {@inheritdoc}
87   */
88  public function validate($value, $constraints = NULL, $groups = NULL) {
89    return $this->startContext($value)
90      ->validate($value, $constraints, $groups)
91      ->getViolations();
92  }
93
94  /**
95   * {@inheritdoc}
96   */
97  public function validateProperty($object, $propertyName, $groups = NULL) {
98    return $this->startContext($object)
99      ->validateProperty($object, $propertyName, $groups)
100      ->getViolations();
101  }
102
103  /**
104   * {@inheritdoc}
105   */
106  public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = NULL) {
107    // Just passing a class name is not supported.
108    if (!is_object($objectOrClass)) {
109      throw new \LogicException('Typed data validation does not support passing the class name only.');
110    }
111    return $this->startContext($objectOrClass)
112      ->validatePropertyValue($objectOrClass, $propertyName, $value, $groups)
113      ->getViolations();
114  }
115
116}
117