[Validator] Decoupled RecursiveContextualValidator from Node
This commit is contained in:
parent
23534ca6ab
commit
38e26fbcaf
@ -16,18 +16,19 @@ use Symfony\Component\Validator\Constraints\GroupSequence;
|
|||||||
use Symfony\Component\Validator\Constraints\Valid;
|
use Symfony\Component\Validator\Constraints\Valid;
|
||||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||||
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||||
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
||||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||||
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||||
use Symfony\Component\Validator\Mapping\GenericMetadata;
|
use Symfony\Component\Validator\Mapping\GenericMetadata;
|
||||||
|
use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
use Symfony\Component\Validator\Node\ClassNode;
|
use Symfony\Component\Validator\Node\ClassNode;
|
||||||
use Symfony\Component\Validator\Node\CollectionNode;
|
use Symfony\Component\Validator\Node\CollectionNode;
|
||||||
use Symfony\Component\Validator\Node\GenericNode;
|
|
||||||
use Symfony\Component\Validator\Node\Node;
|
use Symfony\Component\Validator\Node\Node;
|
||||||
use Symfony\Component\Validator\Node\PropertyNode;
|
use Symfony\Component\Validator\Node\PropertyNode;
|
||||||
use Symfony\Component\Validator\Util\PropertyPath;
|
use Symfony\Component\Validator\Util\PropertyPath;
|
||||||
@ -52,8 +53,6 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
private $validatorFactory;
|
private $validatorFactory;
|
||||||
|
|
||||||
private $currentGroup;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a validator for the given context.
|
* Creates a validator for the given context.
|
||||||
*
|
*
|
||||||
@ -96,12 +95,16 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$metadata->addConstraints($constraints);
|
$metadata->addConstraints($constraints);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
|
||||||
$this->traverseGenericNode(new GenericNode(
|
$this->traverseGenericNode(
|
||||||
$value,
|
$value,
|
||||||
|
null,
|
||||||
$metadata,
|
$metadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups
|
$groups,
|
||||||
), $this->context);
|
null,
|
||||||
|
TraversalStrategy::IMPLICIT,
|
||||||
|
$this->context
|
||||||
|
);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -128,13 +131,16 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
||||||
|
|
||||||
$this->traverseGenericNode(new PropertyNode(
|
$this->traverseGenericNode(
|
||||||
$object,
|
|
||||||
$propertyValue,
|
$propertyValue,
|
||||||
|
$object,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups
|
$groups,
|
||||||
), $this->context);
|
null,
|
||||||
|
TraversalStrategy::IMPLICIT,
|
||||||
|
$this->context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -160,14 +166,16 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
|
||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$this->traverseGenericNode(new PropertyNode(
|
$this->traverseGenericNode(
|
||||||
$object,
|
|
||||||
$value,
|
$value,
|
||||||
|
$object,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups,
|
$groups,
|
||||||
$groups
|
null,
|
||||||
), $this->context);
|
TraversalStrategy::IMPLICIT,
|
||||||
|
$this->context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -218,18 +226,16 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @see CollectionNode
|
* @see CollectionNode
|
||||||
* @see TraversalStrategy
|
* @see TraversalStrategy
|
||||||
*/
|
*/
|
||||||
private function traverseClassNode(ClassNode $node, ExecutionContextInterface $context)
|
private function traverseClassNode($value, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
if (false === $this->validateNode($node, $context)) {
|
$groups = $this->validateNode($value, $value, $metadata, $propertyPath, $groups, $traversalStrategy, $context);
|
||||||
|
|
||||||
|
if (0 === count($groups)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 === count($node->groups)) {
|
foreach ($metadata->getConstrainedProperties() as $propertyName) {
|
||||||
return;
|
foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
|
|
||||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
|
||||||
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
|
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
|
||||||
throw new UnsupportedMetadataException(sprintf(
|
throw new UnsupportedMetadataException(sprintf(
|
||||||
'The property metadata instances should implement '.
|
'The property metadata instances should implement '.
|
||||||
@ -239,26 +245,26 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->traverseGenericNode(new PropertyNode(
|
$this->traverseGenericNode(
|
||||||
$node->value,
|
$propertyMetadata->getPropertyValue($value),
|
||||||
$propertyMetadata->getPropertyValue($node->value),
|
$value,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
$node->propertyPath
|
$propertyPath
|
||||||
? $node->propertyPath.'.'.$propertyName
|
? $propertyPath.'.'.$propertyName
|
||||||
: $propertyName,
|
: $propertyName,
|
||||||
$node->groups,
|
$groups,
|
||||||
$node->cascadedGroups
|
$cascadedGroups,
|
||||||
), $context);
|
TraversalStrategy::IMPLICIT,
|
||||||
|
$context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$traversalStrategy = $node->traversalStrategy;
|
|
||||||
|
|
||||||
// If no specific traversal strategy was requested when this method
|
// If no specific traversal strategy was requested when this method
|
||||||
// was called, use the traversal strategy of the class' metadata
|
// was called, use the traversal strategy of the class' metadata
|
||||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||||
// Keep the STOP_RECURSION flag, if it was set
|
// Keep the STOP_RECURSION flag, if it was set
|
||||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
$traversalStrategy = $metadata->getTraversalStrategy()
|
||||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,18 +274,28 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If IMPLICIT, stop unless we deal with a Traversable
|
// If IMPLICIT, stop unless we deal with a Traversable
|
||||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$node->value instanceof \Traversable) {
|
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$value instanceof \Traversable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
// If TRAVERSE, fail if we have no Traversable
|
||||||
$this->traverseCollectionNode(new CollectionNode(
|
if (!$value instanceof \Traversable) {
|
||||||
$node->value,
|
// Must throw a ConstraintDefinitionException for backwards
|
||||||
$node->propertyPath,
|
// compatibility reasons with Symfony < 2.5
|
||||||
$node->groups,
|
throw new ConstraintDefinitionException(sprintf(
|
||||||
$node->cascadedGroups,
|
'Traversal was enabled for "%s", but this class '.
|
||||||
$traversalStrategy
|
'does not implement "\Traversable".',
|
||||||
), $context);
|
get_class($value)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$value,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,28 +320,26 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @see ClassNode
|
* @see ClassNode
|
||||||
* @see CollectionNode
|
* @see CollectionNode
|
||||||
*/
|
*/
|
||||||
private function traverseCollectionNode(CollectionNode $node, ExecutionContextInterface $context)
|
private function cascadeCollection($collection, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$traversalStrategy = $node->traversalStrategy;
|
|
||||||
|
|
||||||
if ($traversalStrategy & TraversalStrategy::STOP_RECURSION) {
|
if ($traversalStrategy & TraversalStrategy::STOP_RECURSION) {
|
||||||
$traversalStrategy = TraversalStrategy::NONE;
|
$traversalStrategy = TraversalStrategy::NONE;
|
||||||
} else {
|
} else {
|
||||||
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($node->value as $key => $value) {
|
foreach ($collection as $key => $value) {
|
||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
// Arrays are always cascaded, independent of the specified
|
// Arrays are always cascaded, independent of the specified
|
||||||
// traversal strategy
|
// traversal strategy
|
||||||
// (BC with Symfony < 2.5)
|
// (BC with Symfony < 2.5)
|
||||||
$this->traverseCollectionNode(new CollectionNode(
|
$this->cascadeCollection(
|
||||||
$value,
|
$value,
|
||||||
$node->propertyPath.'['.$key.']',
|
$propertyPath.'['.$key.']',
|
||||||
$node->groups,
|
$groups,
|
||||||
null,
|
$traversalStrategy,
|
||||||
$traversalStrategy
|
$context
|
||||||
), $context);
|
);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -335,8 +349,8 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
if (is_object($value)) {
|
if (is_object($value)) {
|
||||||
$this->cascadeObject(
|
$this->cascadeObject(
|
||||||
$value,
|
$value,
|
||||||
$node->propertyPath.'['.$key.']',
|
$propertyPath.'['.$key.']',
|
||||||
$node->groups,
|
$groups,
|
||||||
$traversalStrategy,
|
$traversalStrategy,
|
||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
@ -361,48 +375,45 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @param Node $node The node
|
* @param Node $node The node
|
||||||
* @param ExecutionContextInterface $context The current execution context
|
* @param ExecutionContextInterface $context The current execution context
|
||||||
*/
|
*/
|
||||||
private function traverseGenericNode(Node $node, ExecutionContextInterface $context)
|
private function traverseGenericNode($value, $object, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
if (false === $this->validateNode($node, $context)) {
|
$groups = $this->validateNode($value, $object, $metadata, $propertyPath, $groups, $traversalStrategy, $context);
|
||||||
|
|
||||||
|
if (0 === count($groups)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $node->value) {
|
if (null === $value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "cascadedGroups" property is set by the NodeValidationVisitor when
|
// The "cascadedGroups" property is set by the NodeValidationVisitor when
|
||||||
// traversing group sequences
|
// traversing group sequences
|
||||||
$cascadedGroups = null !== $node->cascadedGroups
|
$cascadedGroups = count($cascadedGroups) > 0
|
||||||
? $node->cascadedGroups
|
? $cascadedGroups
|
||||||
: $node->groups;
|
: $groups;
|
||||||
|
|
||||||
if (0 === count($cascadedGroups)) {
|
$cascadingStrategy = $metadata->getCascadingStrategy();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cascadingStrategy = $node->metadata->getCascadingStrategy();
|
|
||||||
$traversalStrategy = $node->traversalStrategy;
|
|
||||||
|
|
||||||
// If no specific traversal strategy was requested when this method
|
// If no specific traversal strategy was requested when this method
|
||||||
// was called, use the traversal strategy of the node's metadata
|
// was called, use the traversal strategy of the node's metadata
|
||||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||||
// Keep the STOP_RECURSION flag, if it was set
|
// Keep the STOP_RECURSION flag, if it was set
|
||||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
$traversalStrategy = $metadata->getTraversalStrategy()
|
||||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($node->value)) {
|
if (is_array($value)) {
|
||||||
// Arrays are always traversed, independent of the specified
|
// Arrays are always traversed, independent of the specified
|
||||||
// traversal strategy
|
// traversal strategy
|
||||||
// (BC with Symfony < 2.5)
|
// (BC with Symfony < 2.5)
|
||||||
$this->traverseCollectionNode(new CollectionNode(
|
$this->cascadeCollection(
|
||||||
$node->value,
|
$value,
|
||||||
$node->propertyPath,
|
$propertyPath,
|
||||||
$cascadedGroups,
|
$cascadedGroups,
|
||||||
null,
|
$traversalStrategy,
|
||||||
$traversalStrategy
|
$context
|
||||||
), $context);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -412,8 +423,8 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
// a NoSuchMetadataException to be thrown in that case
|
// a NoSuchMetadataException to be thrown in that case
|
||||||
// (BC with Symfony < 2.5)
|
// (BC with Symfony < 2.5)
|
||||||
$this->cascadeObject(
|
$this->cascadeObject(
|
||||||
$node->value,
|
$value,
|
||||||
$node->propertyPath,
|
$propertyPath,
|
||||||
$cascadedGroups,
|
$cascadedGroups,
|
||||||
$traversalStrategy,
|
$traversalStrategy,
|
||||||
$context
|
$context
|
||||||
@ -467,14 +478,15 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->traverseClassNode(new ClassNode(
|
$this->traverseClassNode(
|
||||||
$object,
|
$object,
|
||||||
$classMetadata,
|
$classMetadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
null,
|
null,
|
||||||
$traversalStrategy
|
$traversalStrategy,
|
||||||
), $context);
|
$context
|
||||||
|
);
|
||||||
} catch (NoSuchMetadataException $e) {
|
} catch (NoSuchMetadataException $e) {
|
||||||
// Rethrow if not Traversable
|
// Rethrow if not Traversable
|
||||||
if (!$object instanceof \Traversable) {
|
if (!$object instanceof \Traversable) {
|
||||||
@ -486,13 +498,13 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->traverseCollectionNode(new CollectionNode(
|
$this->cascadeCollection(
|
||||||
$object,
|
$object,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
null,
|
$traversalStrategy,
|
||||||
$traversalStrategy
|
$context
|
||||||
), $context);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,55 +518,19 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @param Node $node The current node
|
* @param Node $node The current node
|
||||||
* @param ExecutionContextInterface $context The execution context
|
* @param ExecutionContextInterface $context The execution context
|
||||||
*
|
*
|
||||||
* @return Boolean Whether to traverse the successor nodes
|
* @return array The groups in which the successor nodes should be validated
|
||||||
*/
|
*/
|
||||||
public function validateNode(Node $node, ExecutionContextInterface $context)
|
public function validateNode($value, $object, MetadataInterface $metadata = null, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
if ($node instanceof CollectionNode) {
|
$context->setValue($value);
|
||||||
return true;
|
$context->setMetadata($metadata);
|
||||||
|
$context->setPropertyPath($propertyPath);
|
||||||
|
|
||||||
|
if ($metadata instanceof ClassMetadataInterface) {
|
||||||
|
$groups = $this->replaceDefaultGroup($value, $metadata, $groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->setValue($node->value);
|
$objectHash = is_object($object) ? spl_object_hash($object) : null;
|
||||||
$context->setMetadata($node->metadata);
|
|
||||||
$context->setPropertyPath($node->propertyPath);
|
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
|
||||||
$groupSequence = null;
|
|
||||||
|
|
||||||
if ($node->metadata->hasGroupSequence()) {
|
|
||||||
// The group sequence is statically defined for the class
|
|
||||||
$groupSequence = $node->metadata->getGroupSequence();
|
|
||||||
} elseif ($node->metadata->isGroupSequenceProvider()) {
|
|
||||||
// The group sequence is dynamically obtained from the validated
|
|
||||||
// object
|
|
||||||
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
|
||||||
$groupSequence = $node->value->getGroupSequence();
|
|
||||||
|
|
||||||
if (!$groupSequence instanceof GroupSequence) {
|
|
||||||
$groupSequence = new GroupSequence($groupSequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $groupSequence) {
|
|
||||||
$key = array_search(Constraint::DEFAULT_GROUP, $node->groups);
|
|
||||||
|
|
||||||
if (false !== $key) {
|
|
||||||
// Replace the "Default" group by the group sequence
|
|
||||||
$node->groups[$key] = $groupSequence;
|
|
||||||
|
|
||||||
// Cascade the "Default" group when validating the sequence
|
|
||||||
$groupSequence->cascadedGroup = Constraint::DEFAULT_GROUP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
|
||||||
$objectHash = spl_object_hash($node->value);
|
|
||||||
} elseif ($node instanceof PropertyNode) {
|
|
||||||
$objectHash = spl_object_hash($node->object);
|
|
||||||
} else {
|
|
||||||
$objectHash = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
||||||
// then call traverse() with each entry of the group sequence and abort
|
// then call traverse() with each entry of the group sequence and abort
|
||||||
@ -562,20 +538,20 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
// finally call traverse() with remaining entries ([G3,G4]) or
|
// finally call traverse() with remaining entries ([G3,G4]) or
|
||||||
// simply continue traversal (if possible)
|
// simply continue traversal (if possible)
|
||||||
|
|
||||||
foreach ($node->groups as $key => $group) {
|
foreach ($groups as $key => $group) {
|
||||||
// Even if we remove the following clause, the constraints on an
|
// Even if we remove the following clause, the constraints on an
|
||||||
// object won't be validated again due to the measures taken in
|
// object won't be validated again due to the measures taken in
|
||||||
// validateNodeForGroup().
|
// validateNodeForGroup().
|
||||||
// The following shortcut, however, prevents validatedNodeForGroup()
|
// The following shortcut, however, prevents validatedNodeForGroup()
|
||||||
// from being called at all and enhances performance a bit.
|
// from being called at all and enhances performance a bit.
|
||||||
if ($node instanceof ClassNode) {
|
if ($metadata instanceof ClassMetadataInterface) {
|
||||||
// Use the object hash for group sequences
|
// Use the object hash for group sequences
|
||||||
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
||||||
|
|
||||||
if ($context->isObjectValidatedForGroup($objectHash, $groupHash)) {
|
if ($context->isObjectValidatedForGroup($objectHash, $groupHash)) {
|
||||||
// Skip this group when validating the successor nodes
|
// Skip this group when validating the successor nodes
|
||||||
// (property and/or collection nodes)
|
// (property and/or collection nodes)
|
||||||
unset($node->groups[$key]);
|
unset($groups[$key]);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -585,19 +561,19 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
// Validate normal group
|
// Validate normal group
|
||||||
if (!$group instanceof GroupSequence) {
|
if (!$group instanceof GroupSequence) {
|
||||||
$this->validateNodeForGroup($node, $group, $context, $objectHash);
|
$this->validateNodeForGroup($value, $objectHash, $metadata, $group, $context);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse group sequence until a violation is generated
|
// Traverse group sequence until a violation is generated
|
||||||
$this->stepThroughGroupSequence($node, $group, $context);
|
$this->stepThroughGroupSequence($value, $object, $metadata, $propertyPath, $traversalStrategy, $group, $context);
|
||||||
|
|
||||||
// Skip the group sequence when validating successor nodes
|
// Skip the group sequence when validating successor nodes
|
||||||
unset($node->groups[$key]);
|
unset($groups[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -610,24 +586,39 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @param GroupSequence $groupSequence The group sequence
|
* @param GroupSequence $groupSequence The group sequence
|
||||||
* @param ExecutionContextInterface $context The execution context
|
* @param ExecutionContextInterface $context The execution context
|
||||||
*/
|
*/
|
||||||
private function stepThroughGroupSequence(Node $node, GroupSequence $groupSequence, ExecutionContextInterface $context)
|
private function stepThroughGroupSequence($value, $object, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$violationCount = count($context->getViolations());
|
$violationCount = count($context->getViolations());
|
||||||
|
|
||||||
foreach ($groupSequence->groups as $groupInSequence) {
|
foreach ($groupSequence->groups as $groupInSequence) {
|
||||||
$node = clone $node;
|
$groups = array($groupInSequence);
|
||||||
$node->groups = array($groupInSequence);
|
$cascadedGroups = null;
|
||||||
|
|
||||||
if (null !== $groupSequence->cascadedGroup) {
|
if (null !== $groupSequence->cascadedGroup) {
|
||||||
$node->cascadedGroups = array($groupSequence->cascadedGroup);
|
$cascadedGroups = array($groupSequence->cascadedGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
if ($metadata instanceof ClassMetadataInterface) {
|
||||||
$this->traverseClassNode($node, $context);
|
$this->traverseClassNode(
|
||||||
} elseif ($node instanceof CollectionNode) {
|
$value,
|
||||||
$this->traverseCollectionNode($node, $context);
|
$metadata,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$cascadedGroups,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->traverseGenericNode($node, $context);
|
$this->traverseGenericNode(
|
||||||
|
$value,
|
||||||
|
$object,
|
||||||
|
$metadata,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$cascadedGroups,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort sequence validation if a violation was generated
|
// Abort sequence validation if a violation was generated
|
||||||
@ -648,25 +639,25 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function validateNodeForGroup(Node $node, $group, ExecutionContextInterface $context, $objectHash)
|
private function validateNodeForGroup($value, $objectHash, MetadataInterface $metadata = null, $group, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$context->setGroup($group);
|
$context->setGroup($group);
|
||||||
|
|
||||||
foreach ($node->metadata->findConstraints($group) as $constraint) {
|
foreach ($metadata->findConstraints($group) as $constraint) {
|
||||||
// Prevent duplicate validation of constraints, in the case
|
// Prevent duplicate validation of constraints, in the case
|
||||||
// that constraints belong to multiple validated groups
|
// that constraints belong to multiple validated groups
|
||||||
if (null !== $objectHash) {
|
if (null !== $objectHash) {
|
||||||
$constraintHash = spl_object_hash($constraint);
|
$constraintHash = spl_object_hash($constraint);
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
if ($metadata instanceof ClassMetadataInterface) {
|
||||||
if ($context->isClassConstraintValidated($objectHash, $constraintHash)) {
|
if ($context->isClassConstraintValidated($objectHash, $constraintHash)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->markClassConstraintAsValidated($objectHash, $constraintHash);
|
$context->markClassConstraintAsValidated($objectHash, $constraintHash);
|
||||||
} elseif ($node instanceof PropertyNode) {
|
} elseif ($metadata instanceof PropertyMetadataInterface) {
|
||||||
$propertyName = $node->metadata->getPropertyName();
|
$propertyName = $metadata->getPropertyName();
|
||||||
|
|
||||||
if ($context->isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash)) {
|
if ($context->isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash)) {
|
||||||
continue;
|
continue;
|
||||||
@ -678,7 +669,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$validator = $this->validatorFactory->getInstance($constraint);
|
$validator = $this->validatorFactory->getInstance($constraint);
|
||||||
$validator->initialize($context);
|
$validator->initialize($context);
|
||||||
$validator->validate($node->value, $constraint);
|
$validator->validate($value, $constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->setGroup(null);
|
$context->setGroup(null);
|
||||||
@ -691,10 +682,42 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @param $value
|
||||||
|
* @param ClassMetadataInterface $metadata
|
||||||
|
* @param array $groups
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getCurrentGroup()
|
private function replaceDefaultGroup($value, ClassMetadataInterface $metadata, array $groups)
|
||||||
{
|
{
|
||||||
return $this->currentGroup;
|
$groupSequence = null;
|
||||||
|
|
||||||
|
if ($metadata->hasGroupSequence()) {
|
||||||
|
// The group sequence is statically defined for the class
|
||||||
|
$groupSequence = $metadata->getGroupSequence();
|
||||||
|
} elseif ($metadata->isGroupSequenceProvider()) {
|
||||||
|
// The group sequence is dynamically obtained from the validated
|
||||||
|
// object
|
||||||
|
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
||||||
|
$groupSequence = $value->getGroupSequence();
|
||||||
|
|
||||||
|
if (!$groupSequence instanceof GroupSequence) {
|
||||||
|
$groupSequence = new GroupSequence($groupSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $groupSequence) {
|
||||||
|
$key = array_search(Constraint::DEFAULT_GROUP, $groups);
|
||||||
|
|
||||||
|
if (false !== $key) {
|
||||||
|
// Replace the "Default" group by the group sequence
|
||||||
|
$groups[$key] = $groupSequence;
|
||||||
|
|
||||||
|
// Cascade the "Default" group when validating the sequence
|
||||||
|
$groupSequence->cascadedGroup = Constraint::DEFAULT_GROUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user