annotate vendor/symfony/validator/Validator/RecursiveContextualValidator.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\Validator\Validator;
Chris@0 13
Chris@0 14 use Symfony\Component\Validator\Constraint;
Chris@0 15 use Symfony\Component\Validator\Constraints\GroupSequence;
Chris@0 16 use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
Chris@0 17 use Symfony\Component\Validator\Context\ExecutionContext;
Chris@0 18 use Symfony\Component\Validator\Context\ExecutionContextInterface;
Chris@0 19 use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
Chris@0 20 use Symfony\Component\Validator\Exception\NoSuchMetadataException;
Chris@0 21 use Symfony\Component\Validator\Exception\RuntimeException;
Chris@0 22 use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
Chris@0 23 use Symfony\Component\Validator\Exception\ValidatorException;
Chris@0 24 use Symfony\Component\Validator\Mapping\CascadingStrategy;
Chris@0 25 use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
Chris@0 26 use Symfony\Component\Validator\Mapping\GenericMetadata;
Chris@0 27 use Symfony\Component\Validator\Mapping\MetadataInterface;
Chris@0 28 use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
Chris@0 29 use Symfony\Component\Validator\Mapping\TraversalStrategy;
Chris@0 30 use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
Chris@0 31 use Symfony\Component\Validator\ObjectInitializerInterface;
Chris@0 32 use Symfony\Component\Validator\Util\PropertyPath;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * Recursive implementation of {@link ContextualValidatorInterface}.
Chris@0 36 *
Chris@0 37 * @author Bernhard Schussek <bschussek@gmail.com>
Chris@0 38 */
Chris@0 39 class RecursiveContextualValidator implements ContextualValidatorInterface
Chris@0 40 {
Chris@0 41 private $context;
Chris@0 42 private $defaultPropertyPath;
Chris@0 43 private $defaultGroups;
Chris@0 44 private $metadataFactory;
Chris@0 45 private $validatorFactory;
Chris@0 46 private $objectInitializers;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * Creates a validator for the given context.
Chris@0 50 *
Chris@0 51 * @param ExecutionContextInterface $context The execution context
Chris@0 52 * @param MetadataFactoryInterface $metadataFactory The factory for
Chris@0 53 * fetching the metadata
Chris@0 54 * of validated objects
Chris@0 55 * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
Chris@0 56 * constraint validators
Chris@0 57 * @param ObjectInitializerInterface[] $objectInitializers The object initializers
Chris@0 58 */
Chris@0 59 public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
Chris@0 60 {
Chris@0 61 $this->context = $context;
Chris@0 62 $this->defaultPropertyPath = $context->getPropertyPath();
Chris@0 63 $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
Chris@0 64 $this->metadataFactory = $metadataFactory;
Chris@0 65 $this->validatorFactory = $validatorFactory;
Chris@0 66 $this->objectInitializers = $objectInitializers;
Chris@0 67 }
Chris@0 68
Chris@0 69 /**
Chris@0 70 * {@inheritdoc}
Chris@0 71 */
Chris@0 72 public function atPath($path)
Chris@0 73 {
Chris@0 74 $this->defaultPropertyPath = $this->context->getPropertyPath($path);
Chris@0 75
Chris@0 76 return $this;
Chris@0 77 }
Chris@0 78
Chris@0 79 /**
Chris@0 80 * {@inheritdoc}
Chris@0 81 */
Chris@0 82 public function validate($value, $constraints = null, $groups = null)
Chris@0 83 {
Chris@0 84 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
Chris@0 85
Chris@0 86 $previousValue = $this->context->getValue();
Chris@0 87 $previousObject = $this->context->getObject();
Chris@0 88 $previousMetadata = $this->context->getMetadata();
Chris@0 89 $previousPath = $this->context->getPropertyPath();
Chris@0 90 $previousGroup = $this->context->getGroup();
Chris@0 91 $previousConstraint = null;
Chris@0 92
Chris@0 93 if ($this->context instanceof ExecutionContext || method_exists($this->context, 'getConstraint')) {
Chris@0 94 $previousConstraint = $this->context->getConstraint();
Chris@0 95 }
Chris@0 96
Chris@0 97 // If explicit constraints are passed, validate the value against
Chris@0 98 // those constraints
Chris@0 99 if (null !== $constraints) {
Chris@0 100 // You can pass a single constraint or an array of constraints
Chris@0 101 // Make sure to deal with an array in the rest of the code
Chris@0 102 if (!is_array($constraints)) {
Chris@0 103 $constraints = array($constraints);
Chris@0 104 }
Chris@0 105
Chris@0 106 $metadata = new GenericMetadata();
Chris@0 107 $metadata->addConstraints($constraints);
Chris@0 108
Chris@0 109 $this->validateGenericNode(
Chris@0 110 $value,
Chris@14 111 $previousObject,
Chris@0 112 is_object($value) ? spl_object_hash($value) : null,
Chris@0 113 $metadata,
Chris@0 114 $this->defaultPropertyPath,
Chris@0 115 $groups,
Chris@0 116 null,
Chris@0 117 TraversalStrategy::IMPLICIT,
Chris@0 118 $this->context
Chris@0 119 );
Chris@0 120
Chris@0 121 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
Chris@0 122 $this->context->setGroup($previousGroup);
Chris@0 123
Chris@0 124 if (null !== $previousConstraint) {
Chris@0 125 $this->context->setConstraint($previousConstraint);
Chris@0 126 }
Chris@0 127
Chris@0 128 return $this;
Chris@0 129 }
Chris@0 130
Chris@0 131 // If an object is passed without explicit constraints, validate that
Chris@0 132 // object against the constraints defined for the object's class
Chris@0 133 if (is_object($value)) {
Chris@0 134 $this->validateObject(
Chris@0 135 $value,
Chris@0 136 $this->defaultPropertyPath,
Chris@0 137 $groups,
Chris@0 138 TraversalStrategy::IMPLICIT,
Chris@0 139 $this->context
Chris@0 140 );
Chris@0 141
Chris@0 142 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
Chris@0 143 $this->context->setGroup($previousGroup);
Chris@0 144
Chris@0 145 return $this;
Chris@0 146 }
Chris@0 147
Chris@0 148 // If an array is passed without explicit constraints, validate each
Chris@0 149 // object in the array
Chris@0 150 if (is_array($value)) {
Chris@0 151 $this->validateEachObjectIn(
Chris@0 152 $value,
Chris@0 153 $this->defaultPropertyPath,
Chris@0 154 $groups,
Chris@0 155 $this->context
Chris@0 156 );
Chris@0 157
Chris@0 158 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
Chris@0 159 $this->context->setGroup($previousGroup);
Chris@0 160
Chris@0 161 return $this;
Chris@0 162 }
Chris@0 163
Chris@0 164 throw new RuntimeException(sprintf(
Chris@0 165 'Cannot validate values of type "%s" automatically. Please '.
Chris@0 166 'provide a constraint.',
Chris@0 167 gettype($value)
Chris@0 168 ));
Chris@0 169 }
Chris@0 170
Chris@0 171 /**
Chris@0 172 * {@inheritdoc}
Chris@0 173 */
Chris@0 174 public function validateProperty($object, $propertyName, $groups = null)
Chris@0 175 {
Chris@0 176 $classMetadata = $this->metadataFactory->getMetadataFor($object);
Chris@0 177
Chris@0 178 if (!$classMetadata instanceof ClassMetadataInterface) {
Chris@0 179 throw new ValidatorException(sprintf(
Chris@0 180 'The metadata factory should return instances of '.
Chris@0 181 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
Chris@0 182 'got: "%s".',
Chris@0 183 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
Chris@0 184 ));
Chris@0 185 }
Chris@0 186
Chris@0 187 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
Chris@0 188 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
Chris@0 189 $cacheKey = spl_object_hash($object);
Chris@0 190 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
Chris@0 191
Chris@0 192 $previousValue = $this->context->getValue();
Chris@0 193 $previousObject = $this->context->getObject();
Chris@0 194 $previousMetadata = $this->context->getMetadata();
Chris@0 195 $previousPath = $this->context->getPropertyPath();
Chris@0 196 $previousGroup = $this->context->getGroup();
Chris@0 197
Chris@0 198 foreach ($propertyMetadatas as $propertyMetadata) {
Chris@0 199 $propertyValue = $propertyMetadata->getPropertyValue($object);
Chris@0 200
Chris@0 201 $this->validateGenericNode(
Chris@0 202 $propertyValue,
Chris@0 203 $object,
Chris@0 204 $cacheKey.':'.get_class($object).':'.$propertyName,
Chris@0 205 $propertyMetadata,
Chris@0 206 $propertyPath,
Chris@0 207 $groups,
Chris@0 208 null,
Chris@0 209 TraversalStrategy::IMPLICIT,
Chris@0 210 $this->context
Chris@0 211 );
Chris@0 212 }
Chris@0 213
Chris@0 214 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
Chris@0 215 $this->context->setGroup($previousGroup);
Chris@0 216
Chris@0 217 return $this;
Chris@0 218 }
Chris@0 219
Chris@0 220 /**
Chris@0 221 * {@inheritdoc}
Chris@0 222 */
Chris@0 223 public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
Chris@0 224 {
Chris@0 225 $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
Chris@0 226
Chris@0 227 if (!$classMetadata instanceof ClassMetadataInterface) {
Chris@0 228 throw new ValidatorException(sprintf(
Chris@0 229 'The metadata factory should return instances of '.
Chris@0 230 '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
Chris@0 231 'got: "%s".',
Chris@0 232 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
Chris@0 233 ));
Chris@0 234 }
Chris@0 235
Chris@0 236 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
Chris@0 237 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
Chris@0 238
Chris@0 239 if (is_object($objectOrClass)) {
Chris@0 240 $object = $objectOrClass;
Chris@14 241 $class = get_class($object);
Chris@0 242 $cacheKey = spl_object_hash($objectOrClass);
Chris@0 243 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
Chris@0 244 } else {
Chris@0 245 // $objectOrClass contains a class name
Chris@0 246 $object = null;
Chris@14 247 $class = $objectOrClass;
Chris@0 248 $cacheKey = null;
Chris@0 249 $propertyPath = $this->defaultPropertyPath;
Chris@0 250 }
Chris@0 251
Chris@0 252 $previousValue = $this->context->getValue();
Chris@0 253 $previousObject = $this->context->getObject();
Chris@0 254 $previousMetadata = $this->context->getMetadata();
Chris@0 255 $previousPath = $this->context->getPropertyPath();
Chris@0 256 $previousGroup = $this->context->getGroup();
Chris@0 257
Chris@0 258 foreach ($propertyMetadatas as $propertyMetadata) {
Chris@0 259 $this->validateGenericNode(
Chris@0 260 $value,
Chris@0 261 $object,
Chris@14 262 $cacheKey.':'.$class.':'.$propertyName,
Chris@0 263 $propertyMetadata,
Chris@0 264 $propertyPath,
Chris@0 265 $groups,
Chris@0 266 null,
Chris@0 267 TraversalStrategy::IMPLICIT,
Chris@0 268 $this->context
Chris@0 269 );
Chris@0 270 }
Chris@0 271
Chris@0 272 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
Chris@0 273 $this->context->setGroup($previousGroup);
Chris@0 274
Chris@0 275 return $this;
Chris@0 276 }
Chris@0 277
Chris@0 278 /**
Chris@0 279 * {@inheritdoc}
Chris@0 280 */
Chris@0 281 public function getViolations()
Chris@0 282 {
Chris@0 283 return $this->context->getViolations();
Chris@0 284 }
Chris@0 285
Chris@0 286 /**
Chris@0 287 * Normalizes the given group or list of groups to an array.
Chris@0 288 *
Chris@0 289 * @param mixed $groups The groups to normalize
Chris@0 290 *
Chris@0 291 * @return array A group array
Chris@0 292 */
Chris@0 293 protected function normalizeGroups($groups)
Chris@0 294 {
Chris@0 295 if (is_array($groups)) {
Chris@0 296 return $groups;
Chris@0 297 }
Chris@0 298
Chris@0 299 return array($groups);
Chris@0 300 }
Chris@0 301
Chris@0 302 /**
Chris@0 303 * Validates an object against the constraints defined for its class.
Chris@0 304 *
Chris@0 305 * If no metadata is available for the class, but the class is an instance
Chris@0 306 * of {@link \Traversable} and the selected traversal strategy allows
Chris@0 307 * traversal, the object will be iterated and each nested object will be
Chris@0 308 * validated instead.
Chris@0 309 *
Chris@0 310 * @param object $object The object to cascade
Chris@0 311 * @param string $propertyPath The current property path
Chris@0 312 * @param string[] $groups The validated groups
Chris@0 313 * @param int $traversalStrategy The strategy for traversing the
Chris@0 314 * cascaded object
Chris@0 315 * @param ExecutionContextInterface $context The current execution context
Chris@0 316 *
Chris@0 317 * @throws NoSuchMetadataException If the object has no associated metadata
Chris@0 318 * and does not implement {@link \Traversable}
Chris@0 319 * or if traversal is disabled via the
Chris@0 320 * $traversalStrategy argument
Chris@0 321 * @throws UnsupportedMetadataException If the metadata returned by the
Chris@0 322 * metadata factory does not implement
Chris@0 323 * {@link ClassMetadataInterface}
Chris@0 324 */
Chris@0 325 private function validateObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
Chris@0 326 {
Chris@0 327 try {
Chris@0 328 $classMetadata = $this->metadataFactory->getMetadataFor($object);
Chris@0 329
Chris@0 330 if (!$classMetadata instanceof ClassMetadataInterface) {
Chris@0 331 throw new UnsupportedMetadataException(sprintf(
Chris@0 332 'The metadata factory should return instances of '.
Chris@0 333 '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
Chris@0 334 'got: "%s".',
Chris@0 335 is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
Chris@0 336 ));
Chris@0 337 }
Chris@0 338
Chris@0 339 $this->validateClassNode(
Chris@0 340 $object,
Chris@0 341 spl_object_hash($object),
Chris@0 342 $classMetadata,
Chris@0 343 $propertyPath,
Chris@0 344 $groups,
Chris@0 345 null,
Chris@0 346 $traversalStrategy,
Chris@0 347 $context
Chris@0 348 );
Chris@0 349 } catch (NoSuchMetadataException $e) {
Chris@0 350 // Rethrow if not Traversable
Chris@0 351 if (!$object instanceof \Traversable) {
Chris@0 352 throw $e;
Chris@0 353 }
Chris@0 354
Chris@0 355 // Rethrow unless IMPLICIT or TRAVERSE
Chris@0 356 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
Chris@0 357 throw $e;
Chris@0 358 }
Chris@0 359
Chris@0 360 $this->validateEachObjectIn(
Chris@0 361 $object,
Chris@0 362 $propertyPath,
Chris@0 363 $groups,
Chris@0 364 $context
Chris@0 365 );
Chris@0 366 }
Chris@0 367 }
Chris@0 368
Chris@0 369 /**
Chris@0 370 * Validates each object in a collection against the constraints defined
Chris@0 371 * for their classes.
Chris@0 372 *
Chris@0 373 * If the parameter $recursive is set to true, nested {@link \Traversable}
Chris@0 374 * objects are iterated as well. Nested arrays are always iterated,
Chris@0 375 * regardless of the value of $recursive.
Chris@0 376 *
Chris@14 377 * @param iterable $collection The collection
Chris@0 378 * @param string $propertyPath The current property path
Chris@0 379 * @param string[] $groups The validated groups
Chris@0 380 * @param ExecutionContextInterface $context The current execution context
Chris@0 381 *
Chris@0 382 * @see ClassNode
Chris@0 383 * @see CollectionNode
Chris@0 384 */
Chris@0 385 private function validateEachObjectIn($collection, $propertyPath, array $groups, ExecutionContextInterface $context)
Chris@0 386 {
Chris@0 387 foreach ($collection as $key => $value) {
Chris@0 388 if (is_array($value)) {
Chris@0 389 // Arrays are always cascaded, independent of the specified
Chris@0 390 // traversal strategy
Chris@0 391 $this->validateEachObjectIn(
Chris@0 392 $value,
Chris@0 393 $propertyPath.'['.$key.']',
Chris@0 394 $groups,
Chris@0 395 $context
Chris@0 396 );
Chris@0 397
Chris@0 398 continue;
Chris@0 399 }
Chris@0 400
Chris@0 401 // Scalar and null values in the collection are ignored
Chris@0 402 if (is_object($value)) {
Chris@0 403 $this->validateObject(
Chris@0 404 $value,
Chris@0 405 $propertyPath.'['.$key.']',
Chris@0 406 $groups,
Chris@0 407 TraversalStrategy::IMPLICIT,
Chris@0 408 $context
Chris@0 409 );
Chris@0 410 }
Chris@0 411 }
Chris@0 412 }
Chris@0 413
Chris@0 414 /**
Chris@0 415 * Validates a class node.
Chris@0 416 *
Chris@0 417 * A class node is a combination of an object with a {@link ClassMetadataInterface}
Chris@0 418 * instance. Each class node (conceptionally) has zero or more succeeding
Chris@0 419 * property nodes:
Chris@0 420 *
Chris@0 421 * (Article:class node)
Chris@0 422 * \
Chris@0 423 * ($title:property node)
Chris@0 424 *
Chris@0 425 * This method validates the passed objects against all constraints defined
Chris@0 426 * at class level. It furthermore triggers the validation of each of the
Chris@0 427 * class' properties against the constraints for that property.
Chris@0 428 *
Chris@0 429 * If the selected traversal strategy allows traversal, the object is
Chris@0 430 * iterated and each nested object is validated against its own constraints.
Chris@0 431 * The object is not traversed if traversal is disabled in the class
Chris@0 432 * metadata.
Chris@0 433 *
Chris@0 434 * If the passed groups contain the group "Default", the validator will
Chris@0 435 * check whether the "Default" group has been replaced by a group sequence
Chris@0 436 * in the class metadata. If this is the case, the group sequence is
Chris@0 437 * validated instead.
Chris@0 438 *
Chris@0 439 * @param object $object The validated object
Chris@0 440 * @param string $cacheKey The key for caching
Chris@0 441 * the validated object
Chris@0 442 * @param ClassMetadataInterface $metadata The class metadata of
Chris@0 443 * the object
Chris@0 444 * @param string $propertyPath The property path leading
Chris@0 445 * to the object
Chris@0 446 * @param string[] $groups The groups in which the
Chris@0 447 * object should be validated
Chris@0 448 * @param string[]|null $cascadedGroups The groups in which
Chris@0 449 * cascaded objects should
Chris@0 450 * be validated
Chris@0 451 * @param int $traversalStrategy The strategy used for
Chris@0 452 * traversing the object
Chris@0 453 * @param ExecutionContextInterface $context The current execution context
Chris@0 454 *
Chris@0 455 * @throws UnsupportedMetadataException If a property metadata does not
Chris@0 456 * implement {@link PropertyMetadataInterface}
Chris@0 457 * @throws ConstraintDefinitionException If traversal was enabled but the
Chris@0 458 * object does not implement
Chris@0 459 * {@link \Traversable}
Chris@0 460 *
Chris@0 461 * @see TraversalStrategy
Chris@0 462 */
Chris@0 463 private function validateClassNode($object, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
Chris@0 464 {
Chris@0 465 $context->setNode($object, $object, $metadata, $propertyPath);
Chris@0 466
Chris@0 467 if (!$context->isObjectInitialized($cacheKey)) {
Chris@0 468 foreach ($this->objectInitializers as $initializer) {
Chris@0 469 $initializer->initialize($object);
Chris@0 470 }
Chris@0 471
Chris@0 472 $context->markObjectAsInitialized($cacheKey);
Chris@0 473 }
Chris@0 474
Chris@0 475 foreach ($groups as $key => $group) {
Chris@0 476 // If the "Default" group is replaced by a group sequence, remember
Chris@0 477 // to cascade the "Default" group when traversing the group
Chris@0 478 // sequence
Chris@0 479 $defaultOverridden = false;
Chris@0 480
Chris@0 481 // Use the object hash for group sequences
Chris@0 482 $groupHash = is_object($group) ? spl_object_hash($group) : $group;
Chris@0 483
Chris@0 484 if ($context->isGroupValidated($cacheKey, $groupHash)) {
Chris@0 485 // Skip this group when validating the properties and when
Chris@0 486 // traversing the object
Chris@0 487 unset($groups[$key]);
Chris@0 488
Chris@0 489 continue;
Chris@0 490 }
Chris@0 491
Chris@0 492 $context->markGroupAsValidated($cacheKey, $groupHash);
Chris@0 493
Chris@0 494 // Replace the "Default" group by the group sequence defined
Chris@0 495 // for the class, if applicable.
Chris@0 496 // This is done after checking the cache, so that
Chris@0 497 // spl_object_hash() isn't called for this sequence and
Chris@0 498 // "Default" is used instead in the cache. This is useful
Chris@0 499 // if the getters below return different group sequences in
Chris@0 500 // every call.
Chris@0 501 if (Constraint::DEFAULT_GROUP === $group) {
Chris@0 502 if ($metadata->hasGroupSequence()) {
Chris@0 503 // The group sequence is statically defined for the class
Chris@0 504 $group = $metadata->getGroupSequence();
Chris@0 505 $defaultOverridden = true;
Chris@0 506 } elseif ($metadata->isGroupSequenceProvider()) {
Chris@0 507 // The group sequence is dynamically obtained from the validated
Chris@0 508 // object
Chris@0 509 /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
Chris@0 510 $group = $object->getGroupSequence();
Chris@0 511 $defaultOverridden = true;
Chris@0 512
Chris@0 513 if (!$group instanceof GroupSequence) {
Chris@0 514 $group = new GroupSequence($group);
Chris@0 515 }
Chris@0 516 }
Chris@0 517 }
Chris@0 518
Chris@0 519 // If the groups (=[<G1,G2>,G3,G4]) contain a group sequence
Chris@0 520 // (=<G1,G2>), then call validateClassNode() with each entry of the
Chris@0 521 // group sequence and abort if necessary (G1, G2)
Chris@0 522 if ($group instanceof GroupSequence) {
Chris@0 523 $this->stepThroughGroupSequence(
Chris@0 524 $object,
Chris@0 525 $object,
Chris@0 526 $cacheKey,
Chris@0 527 $metadata,
Chris@0 528 $propertyPath,
Chris@0 529 $traversalStrategy,
Chris@0 530 $group,
Chris@0 531 $defaultOverridden ? Constraint::DEFAULT_GROUP : null,
Chris@0 532 $context
Chris@0 533 );
Chris@0 534
Chris@0 535 // Skip the group sequence when validating properties, because
Chris@0 536 // stepThroughGroupSequence() already validates the properties
Chris@0 537 unset($groups[$key]);
Chris@0 538
Chris@0 539 continue;
Chris@0 540 }
Chris@0 541
Chris@0 542 $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
Chris@0 543 }
Chris@0 544
Chris@0 545 // If no more groups should be validated for the property nodes,
Chris@0 546 // we can safely quit
Chris@0 547 if (0 === count($groups)) {
Chris@0 548 return;
Chris@0 549 }
Chris@0 550
Chris@0 551 // Validate all properties against their constraints
Chris@0 552 foreach ($metadata->getConstrainedProperties() as $propertyName) {
Chris@0 553 // If constraints are defined both on the getter of a property as
Chris@0 554 // well as on the property itself, then getPropertyMetadata()
Chris@0 555 // returns two metadata objects, not just one
Chris@0 556 foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
Chris@0 557 if (!$propertyMetadata instanceof PropertyMetadataInterface) {
Chris@0 558 throw new UnsupportedMetadataException(sprintf(
Chris@0 559 'The property metadata instances should implement '.
Chris@0 560 '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
Chris@0 561 'got: "%s".',
Chris@0 562 is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
Chris@0 563 ));
Chris@0 564 }
Chris@0 565
Chris@0 566 $propertyValue = $propertyMetadata->getPropertyValue($object);
Chris@0 567
Chris@0 568 $this->validateGenericNode(
Chris@0 569 $propertyValue,
Chris@0 570 $object,
Chris@0 571 $cacheKey.':'.get_class($object).':'.$propertyName,
Chris@0 572 $propertyMetadata,
Chris@0 573 PropertyPath::append($propertyPath, $propertyName),
Chris@0 574 $groups,
Chris@0 575 $cascadedGroups,
Chris@0 576 TraversalStrategy::IMPLICIT,
Chris@0 577 $context
Chris@0 578 );
Chris@0 579 }
Chris@0 580 }
Chris@0 581
Chris@0 582 // If no specific traversal strategy was requested when this method
Chris@0 583 // was called, use the traversal strategy of the class' metadata
Chris@0 584 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
Chris@0 585 $traversalStrategy = $metadata->getTraversalStrategy();
Chris@0 586 }
Chris@0 587
Chris@0 588 // Traverse only if IMPLICIT or TRAVERSE
Chris@0 589 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
Chris@0 590 return;
Chris@0 591 }
Chris@0 592
Chris@0 593 // If IMPLICIT, stop unless we deal with a Traversable
Chris@0 594 if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) {
Chris@0 595 return;
Chris@0 596 }
Chris@0 597
Chris@0 598 // If TRAVERSE, fail if we have no Traversable
Chris@0 599 if (!$object instanceof \Traversable) {
Chris@0 600 throw new ConstraintDefinitionException(sprintf(
Chris@0 601 'Traversal was enabled for "%s", but this class '.
Chris@0 602 'does not implement "\Traversable".',
Chris@0 603 get_class($object)
Chris@0 604 ));
Chris@0 605 }
Chris@0 606
Chris@0 607 $this->validateEachObjectIn(
Chris@0 608 $object,
Chris@0 609 $propertyPath,
Chris@0 610 $groups,
Chris@0 611 $context
Chris@0 612 );
Chris@0 613 }
Chris@0 614
Chris@0 615 /**
Chris@0 616 * Validates a node that is not a class node.
Chris@0 617 *
Chris@0 618 * Currently, two such node types exist:
Chris@0 619 *
Chris@0 620 * - property nodes, which consist of the value of an object's
Chris@0 621 * property together with a {@link PropertyMetadataInterface} instance
Chris@0 622 * - generic nodes, which consist of a value and some arbitrary
Chris@0 623 * constraints defined in a {@link MetadataInterface} container
Chris@0 624 *
Chris@0 625 * In both cases, the value is validated against all constraints defined
Chris@0 626 * in the passed metadata object. Then, if the value is an instance of
Chris@0 627 * {@link \Traversable} and the selected traversal strategy permits it,
Chris@0 628 * the value is traversed and each nested object validated against its own
Chris@0 629 * constraints. Arrays are always traversed.
Chris@0 630 *
Chris@0 631 * @param mixed $value The validated value
Chris@0 632 * @param object|null $object The current object
Chris@0 633 * @param string $cacheKey The key for caching
Chris@0 634 * the validated value
Chris@0 635 * @param MetadataInterface $metadata The metadata of the
Chris@0 636 * value
Chris@0 637 * @param string $propertyPath The property path leading
Chris@0 638 * to the value
Chris@0 639 * @param string[] $groups The groups in which the
Chris@0 640 * value should be validated
Chris@0 641 * @param string[]|null $cascadedGroups The groups in which
Chris@0 642 * cascaded objects should
Chris@0 643 * be validated
Chris@0 644 * @param int $traversalStrategy The strategy used for
Chris@0 645 * traversing the value
Chris@0 646 * @param ExecutionContextInterface $context The current execution context
Chris@0 647 *
Chris@0 648 * @see TraversalStrategy
Chris@0 649 */
Chris@0 650 private function validateGenericNode($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
Chris@0 651 {
Chris@0 652 $context->setNode($value, $object, $metadata, $propertyPath);
Chris@0 653
Chris@0 654 foreach ($groups as $key => $group) {
Chris@0 655 if ($group instanceof GroupSequence) {
Chris@0 656 $this->stepThroughGroupSequence(
Chris@0 657 $value,
Chris@0 658 $object,
Chris@0 659 $cacheKey,
Chris@0 660 $metadata,
Chris@0 661 $propertyPath,
Chris@0 662 $traversalStrategy,
Chris@0 663 $group,
Chris@0 664 null,
Chris@0 665 $context
Chris@0 666 );
Chris@0 667
Chris@0 668 // Skip the group sequence when cascading, as the cascading
Chris@0 669 // logic is already done in stepThroughGroupSequence()
Chris@0 670 unset($groups[$key]);
Chris@0 671
Chris@0 672 continue;
Chris@0 673 }
Chris@0 674
Chris@0 675 $this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
Chris@0 676 }
Chris@0 677
Chris@0 678 if (0 === count($groups)) {
Chris@0 679 return;
Chris@0 680 }
Chris@0 681
Chris@0 682 if (null === $value) {
Chris@0 683 return;
Chris@0 684 }
Chris@0 685
Chris@0 686 $cascadingStrategy = $metadata->getCascadingStrategy();
Chris@0 687
Chris@0 688 // Quit unless we have an array or a cascaded object
Chris@0 689 if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) {
Chris@0 690 return;
Chris@0 691 }
Chris@0 692
Chris@0 693 // If no specific traversal strategy was requested when this method
Chris@0 694 // was called, use the traversal strategy of the node's metadata
Chris@0 695 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
Chris@0 696 $traversalStrategy = $metadata->getTraversalStrategy();
Chris@0 697 }
Chris@0 698
Chris@0 699 // The $cascadedGroups property is set, if the "Default" group is
Chris@0 700 // overridden by a group sequence
Chris@0 701 // See validateClassNode()
Chris@0 702 $cascadedGroups = null !== $cascadedGroups && count($cascadedGroups) > 0 ? $cascadedGroups : $groups;
Chris@0 703
Chris@0 704 if (is_array($value)) {
Chris@0 705 // Arrays are always traversed, independent of the specified
Chris@0 706 // traversal strategy
Chris@0 707 $this->validateEachObjectIn(
Chris@0 708 $value,
Chris@0 709 $propertyPath,
Chris@0 710 $cascadedGroups,
Chris@0 711 $context
Chris@0 712 );
Chris@0 713
Chris@0 714 return;
Chris@0 715 }
Chris@0 716
Chris@0 717 // If the value is a scalar, pass it anyway, because we want
Chris@0 718 // a NoSuchMetadataException to be thrown in that case
Chris@0 719 $this->validateObject(
Chris@0 720 $value,
Chris@0 721 $propertyPath,
Chris@0 722 $cascadedGroups,
Chris@0 723 $traversalStrategy,
Chris@0 724 $context
Chris@0 725 );
Chris@0 726
Chris@0 727 // Currently, the traversal strategy can only be TRAVERSE for a
Chris@0 728 // generic node if the cascading strategy is CASCADE. Thus, traversable
Chris@0 729 // objects will always be handled within validateObject() and there's
Chris@0 730 // nothing more to do here.
Chris@0 731
Chris@0 732 // see GenericMetadata::addConstraint()
Chris@0 733 }
Chris@0 734
Chris@0 735 /**
Chris@0 736 * Sequentially validates a node's value in each group of a group sequence.
Chris@0 737 *
Chris@0 738 * If any of the constraints generates a violation, subsequent groups in the
Chris@0 739 * group sequence are skipped.
Chris@0 740 *
Chris@0 741 * @param mixed $value The validated value
Chris@0 742 * @param object|null $object The current object
Chris@0 743 * @param string $cacheKey The key for caching
Chris@0 744 * the validated value
Chris@0 745 * @param MetadataInterface $metadata The metadata of the
Chris@0 746 * value
Chris@0 747 * @param string $propertyPath The property path leading
Chris@0 748 * to the value
Chris@0 749 * @param int $traversalStrategy The strategy used for
Chris@0 750 * traversing the value
Chris@0 751 * @param GroupSequence $groupSequence The group sequence
Chris@0 752 * @param string|null $cascadedGroup The group that should
Chris@0 753 * be passed to cascaded
Chris@0 754 * objects instead of
Chris@0 755 * the group sequence
Chris@0 756 * @param ExecutionContextInterface $context The execution context
Chris@0 757 */
Chris@0 758 private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
Chris@0 759 {
Chris@0 760 $violationCount = count($context->getViolations());
Chris@0 761 $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
Chris@0 762
Chris@0 763 foreach ($groupSequence->groups as $groupInSequence) {
Chris@0 764 $groups = (array) $groupInSequence;
Chris@0 765
Chris@0 766 if ($metadata instanceof ClassMetadataInterface) {
Chris@0 767 $this->validateClassNode(
Chris@0 768 $value,
Chris@0 769 $cacheKey,
Chris@0 770 $metadata,
Chris@0 771 $propertyPath,
Chris@0 772 $groups,
Chris@0 773 $cascadedGroups,
Chris@0 774 $traversalStrategy,
Chris@0 775 $context
Chris@0 776 );
Chris@0 777 } else {
Chris@0 778 $this->validateGenericNode(
Chris@0 779 $value,
Chris@0 780 $object,
Chris@0 781 $cacheKey,
Chris@0 782 $metadata,
Chris@0 783 $propertyPath,
Chris@0 784 $groups,
Chris@0 785 $cascadedGroups,
Chris@0 786 $traversalStrategy,
Chris@0 787 $context
Chris@0 788 );
Chris@0 789 }
Chris@0 790
Chris@0 791 // Abort sequence validation if a violation was generated
Chris@0 792 if (count($context->getViolations()) > $violationCount) {
Chris@0 793 break;
Chris@0 794 }
Chris@0 795 }
Chris@0 796 }
Chris@0 797
Chris@0 798 /**
Chris@0 799 * Validates a node's value against all constraints in the given group.
Chris@0 800 *
Chris@0 801 * @param mixed $value The validated value
Chris@0 802 * @param string $cacheKey The key for caching the
Chris@0 803 * validated value
Chris@0 804 * @param MetadataInterface $metadata The metadata of the value
Chris@0 805 * @param string $group The group to validate
Chris@0 806 * @param ExecutionContextInterface $context The execution context
Chris@0 807 */
Chris@0 808 private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, $group, ExecutionContextInterface $context)
Chris@0 809 {
Chris@0 810 $context->setGroup($group);
Chris@0 811
Chris@0 812 foreach ($metadata->findConstraints($group) as $constraint) {
Chris@0 813 // Prevent duplicate validation of constraints, in the case
Chris@0 814 // that constraints belong to multiple validated groups
Chris@0 815 if (null !== $cacheKey) {
Chris@0 816 $constraintHash = spl_object_hash($constraint);
Chris@0 817
Chris@0 818 if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
Chris@0 819 continue;
Chris@0 820 }
Chris@0 821
Chris@0 822 $context->markConstraintAsValidated($cacheKey, $constraintHash);
Chris@0 823 }
Chris@0 824
Chris@0 825 $context->setConstraint($constraint);
Chris@0 826
Chris@0 827 $validator = $this->validatorFactory->getInstance($constraint);
Chris@0 828 $validator->initialize($context);
Chris@0 829 $validator->validate($value, $constraint);
Chris@0 830 }
Chris@0 831 }
Chris@0 832 }