PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php

http://github.com/drupal/drupal
PHP | 245 lines | 114 code | 29 blank | 102 comment | 19 complexity | 41040151191fdfee85efde999fb92720 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\TypedData\Validation;
  3. use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
  4. use Drupal\Core\TypedData\ComplexDataInterface;
  5. use Drupal\Core\TypedData\ListInterface;
  6. use Drupal\Core\TypedData\TypedDataInterface;
  7. use Drupal\Core\TypedData\TypedDataManagerInterface;
  8. use Symfony\Component\Validator\Constraint;
  9. use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
  10. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  11. use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
  12. use Symfony\Component\Validator\Util\PropertyPath;
  13. /**
  14. * Defines a recursive contextual validator for Typed Data.
  15. *
  16. * For both list and complex data it call recursively out to the properties /
  17. * elements of the list.
  18. *
  19. * This class calls out to some methods on the execution context marked as
  20. * internal. These methods are internal to the validator (which is implemented
  21. * by this class) but should not be called by users.
  22. * See http://symfony.com/doc/current/contributing/code/bc.html for more
  23. * information about @internal.
  24. *
  25. * @see \Drupal\Core\TypedData\Validation\RecursiveValidator::startContext()
  26. * @see \Drupal\Core\TypedData\Validation\RecursiveValidator::inContext()
  27. */
  28. class RecursiveContextualValidator implements ContextualValidatorInterface {
  29. /**
  30. * The execution context.
  31. *
  32. * @var \Symfony\Component\Validator\Context\ExecutionContextInterface
  33. */
  34. protected $context;
  35. /**
  36. * The metadata factory.
  37. *
  38. * @var \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface
  39. */
  40. protected $metadataFactory;
  41. /**
  42. * The constraint validator factory.
  43. *
  44. * @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
  45. */
  46. protected $constraintValidatorFactory;
  47. /**
  48. * Creates a validator for the given context.
  49. *
  50. * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
  51. * The factory for creating new contexts.
  52. * @param \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface $metadata_factory
  53. * The metadata factory.
  54. * @param \Symfony\Component\Validator\ConstraintValidatorFactoryInterface $validator_factory
  55. * The constraint validator factory.
  56. * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
  57. * The typed data manager.
  58. */
  59. public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadata_factory, ConstraintValidatorFactoryInterface $validator_factory, TypedDataManagerInterface $typed_data_manager) {
  60. $this->context = $context;
  61. $this->metadataFactory = $metadata_factory;
  62. $this->constraintValidatorFactory = $validator_factory;
  63. $this->typedDataManager = $typed_data_manager;
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. public function atPath($path) {
  69. // @todo This method is not used at the moment, see
  70. // https://www.drupal.org/node/2482527
  71. return $this;
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. public function validate($data, $constraints = NULL, $groups = NULL, $is_root_call = TRUE) {
  77. if (isset($groups)) {
  78. throw new \LogicException('Passing custom groups is not supported.');
  79. }
  80. if (!$data instanceof TypedDataInterface) {
  81. throw new \InvalidArgumentException('The passed value must be a typed data object.');
  82. }
  83. // You can pass a single constraint or an array of constraints.
  84. // Make sure to deal with an array in the rest of the code.
  85. if (isset($constraints) && !is_array($constraints)) {
  86. $constraints = [$constraints];
  87. }
  88. $this->validateNode($data, $constraints, $is_root_call);
  89. return $this;
  90. }
  91. /**
  92. * Validates a Typed Data node in the validation tree.
  93. *
  94. * If no constraints are passed, the data is validated against the
  95. * constraints specified in its data definition. If the data is complex or a
  96. * list and no constraints are passed, the contained properties or list items
  97. * are validated recursively.
  98. *
  99. * @param \Drupal\Core\TypedData\TypedDataInterface $data
  100. * The data to validated.
  101. * @param \Symfony\Component\Validator\Constraint[]|null $constraints
  102. * (optional) If set, an array of constraints to validate.
  103. * @param bool $is_root_call
  104. * (optional) Whether its the most upper call in the type data tree.
  105. *
  106. * @return $this
  107. */
  108. protected function validateNode(TypedDataInterface $data, $constraints = NULL, $is_root_call = FALSE) {
  109. $previous_value = $this->context->getValue();
  110. $previous_object = $this->context->getObject();
  111. $previous_metadata = $this->context->getMetadata();
  112. $previous_path = $this->context->getPropertyPath();
  113. $metadata = $this->metadataFactory->getMetadataFor($data);
  114. $cache_key = spl_object_hash($data);
  115. $property_path = $is_root_call ? '' : PropertyPath::append($previous_path, $data->getName());
  116. // Prefer a specific instance of the typed data manager stored by the data
  117. // if it is available. This is necessary for specialized typed data objects,
  118. // for example those using the typed config subclass of the manager.
  119. $typed_data_manager = method_exists($data, 'getTypedDataManager') ? $data->getTypedDataManager() : $this->typedDataManager;
  120. // Pass the canonical representation of the data as validated value to
  121. // constraint validators, such that they do not have to care about Typed
  122. // Data.
  123. $value = $typed_data_manager->getCanonicalRepresentation($data);
  124. $constraints_given = isset($constraints);
  125. $this->context->setNode($value, $data, $metadata, $property_path);
  126. if (isset($constraints) || !$this->context->isGroupValidated($cache_key, Constraint::DEFAULT_GROUP)) {
  127. if (!isset($constraints)) {
  128. $this->context->markGroupAsValidated($cache_key, Constraint::DEFAULT_GROUP);
  129. $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP);
  130. }
  131. $this->validateConstraints($value, $cache_key, $constraints);
  132. }
  133. // If the data is a list or complex data, validate the contained list items
  134. // or properties. However, do not recurse if the data is empty.
  135. // Next, we do not recurse if given constraints are validated against an
  136. // entity, since we should determine whether the entity matches the
  137. // constraints and not whether the entity validates.
  138. if (($data instanceof ListInterface || $data instanceof ComplexDataInterface) && !$data->isEmpty() && !($data instanceof EntityAdapter && $constraints_given)) {
  139. foreach ($data as $name => $property) {
  140. $this->validateNode($property);
  141. }
  142. }
  143. $this->context->setNode($previous_value, $previous_object, $previous_metadata, $previous_path);
  144. return $this;
  145. }
  146. /**
  147. * Validates a node's value against all constraints in the given group.
  148. *
  149. * @param mixed $value
  150. * The validated value.
  151. * @param string $cache_key
  152. * The cache key used internally to ensure we don't validate the same
  153. * constraint twice.
  154. * @param \Symfony\Component\Validator\Constraint[] $constraints
  155. * The constraints which should be ensured for the given value.
  156. */
  157. protected function validateConstraints($value, $cache_key, $constraints) {
  158. foreach ($constraints as $constraint) {
  159. // Prevent duplicate validation of constraints, in the case
  160. // that constraints belong to multiple validated groups
  161. if (isset($cache_key)) {
  162. $constraint_hash = spl_object_hash($constraint);
  163. if ($this->context->isConstraintValidated($cache_key, $constraint_hash)) {
  164. continue;
  165. }
  166. $this->context->markConstraintAsValidated($cache_key, $constraint_hash);
  167. }
  168. $this->context->setConstraint($constraint);
  169. $validator = $this->constraintValidatorFactory->getInstance($constraint);
  170. $validator->initialize($this->context);
  171. $validator->validate($value, $constraint);
  172. }
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function getViolations() {
  178. return $this->context->getViolations();
  179. }
  180. /**
  181. * {@inheritdoc}
  182. */
  183. public function validateProperty($object, $propertyName, $groups = NULL) {
  184. if (isset($groups)) {
  185. throw new \LogicException('Passing custom groups is not supported.');
  186. }
  187. if (!is_object($object)) {
  188. throw new \InvalidArgumentException('Passing class name is not supported.');
  189. }
  190. elseif (!$object instanceof TypedDataInterface) {
  191. throw new \InvalidArgumentException('The passed in object has to be typed data.');
  192. }
  193. elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) {
  194. throw new \InvalidArgumentException('Passed data does not contain properties.');
  195. }
  196. return $this->validateNode($object->get($propertyName), NULL, TRUE);
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. public function validatePropertyValue($object, $property_name, $value, $groups = NULL) {
  202. if (!is_object($object)) {
  203. throw new \InvalidArgumentException('Passing class name is not supported.');
  204. }
  205. elseif (!$object instanceof TypedDataInterface) {
  206. throw new \InvalidArgumentException('The passed in object has to be typed data.');
  207. }
  208. elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) {
  209. throw new \InvalidArgumentException('Passed data does not contain properties.');
  210. }
  211. $data = $object->get($property_name);
  212. $metadata = $this->metadataFactory->getMetadataFor($data);
  213. $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP);
  214. return $this->validate($value, $constraints, $groups, TRUE);
  215. }
  216. }