[Validator] Moved logic of replaceDefaultGroup() to validateNode()

This commit is contained in:
Bernhard Schussek 2014-03-17 18:28:47 +01:00
parent 2f23d9725b
commit 029a71638e
2 changed files with 83 additions and 119 deletions

View File

@ -72,8 +72,6 @@ class NodeValidationVisitor extends AbstractVisitor
$context->setNode($node->value, $node->metadata, $node->propertyPath); $context->setNode($node->value, $node->metadata, $node->propertyPath);
if ($node instanceof ClassNode) { if ($node instanceof ClassNode) {
$this->replaceDefaultGroup($node);
$objectHash = spl_object_hash($node->value); $objectHash = spl_object_hash($node->value);
} elseif ($node instanceof PropertyNode) { } elseif ($node instanceof PropertyNode) {
$objectHash = spl_object_hash($node->object); $objectHash = spl_object_hash($node->object);
@ -88,6 +86,8 @@ class NodeValidationVisitor extends AbstractVisitor
// simply continue traversal (if possible) // simply continue traversal (if possible)
foreach ($node->groups as $key => $group) { foreach ($node->groups as $key => $group) {
$cascadedGroup = null;
// 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().
@ -106,11 +106,36 @@ class NodeValidationVisitor extends AbstractVisitor
} }
$context->markObjectAsValidatedForGroup($objectHash, $groupHash); $context->markObjectAsValidatedForGroup($objectHash, $groupHash);
// Replace the "Default" group by the group sequence defined
// for the class, if applicable
// This is done after checking the cache, so that
// spl_object_hash() isn't called for this sequence and
// "Default" is used instead in the cache. This is useful
// if the getters below return different group sequences in
// every call.
if (Constraint::DEFAULT_GROUP === $group) {
if ($node->metadata->hasGroupSequence()) {
// The group sequence is statically defined for the class
$group = $node->metadata->getGroupSequence();
$cascadedGroup = Constraint::DEFAULT_GROUP;
} elseif ($node->metadata->isGroupSequenceProvider()) {
// The group sequence is dynamically obtained from the validated
// object
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
$group = $node->value->getGroupSequence();
$cascadedGroup = Constraint::DEFAULT_GROUP;
if (!$group instanceof GroupSequence) {
$group = new GroupSequence($group);
}
}
}
} }
if ($group instanceof GroupSequence) { if ($group instanceof GroupSequence) {
// Traverse group sequence until a violation is generated // Traverse group sequence until a violation is generated
$this->traverseGroupSequence($node, $group, $context); $this->traverseGroupSequence($node, $group, $cascadedGroup, $context);
// Skip the group sequence when validating successor nodes // Skip the group sequence when validating successor nodes
unset($node->groups[$key]); unset($node->groups[$key]);
@ -135,17 +160,15 @@ class NodeValidationVisitor extends AbstractVisitor
* @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 traverseGroupSequence(Node $node, GroupSequence $groupSequence, ExecutionContextInterface $context) private function traverseGroupSequence(Node $node, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
{ {
$violationCount = count($context->getViolations()); $violationCount = count($context->getViolations());
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
foreach ($groupSequence->groups as $groupInSequence) { foreach ($groupSequence->groups as $groupInSequence) {
$node = clone $node; $node = clone $node;
$node->groups = array($groupInSequence); $node->groups = array($groupInSequence);
$node->cascadedGroups = $cascadedGroups;
if (null !== $groupSequence->cascadedGroup) {
$node->cascadedGroups = array($groupSequence->cascadedGroup);
}
$this->nodeTraverser->traverse(array($node), $context); $this->nodeTraverser->traverse(array($node), $context);
@ -199,44 +222,4 @@ class NodeValidationVisitor extends AbstractVisitor
$validator->validate($node->value, $constraint); $validator->validate($node->value, $constraint);
} }
} }
/**
* Checks class nodes whether their "Default" group is replaced by a group
* sequence and adjusts the validation groups accordingly.
*
* If the "Default" group is replaced for a class node, and if the validated
* groups of the node contain the group "Default", that group is replaced by
* the group sequence specified in the class' metadata.
*
* @param ClassNode $node The node
*/
private function replaceDefaultGroup(ClassNode $node)
{
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);
}
} else {
// The "Default" group is not overridden. Quit.
return;
}
$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;
}
}
} }

View File

@ -277,9 +277,6 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
*/ */
private function traverseClassNode($value, $valueHash, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context) private function traverseClassNode($value, $valueHash, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
{ {
// Replace "Default" group by group sequence, if appropriate
$groups = $this->replaceDefaultGroup($value, $metadata, $groups);
$groups = $this->validateNode($value, $valueHash, null, null, $metadata, $propertyPath, $groups, $traversalStrategy, $context); $groups = $this->validateNode($value, $valueHash, null, null, $metadata, $propertyPath, $groups, $traversalStrategy, $context);
if (0 === count($groups)) { if (0 === count($groups)) {
@ -444,14 +441,13 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
return; return;
} }
// The "cascadedGroups" property is set by the NodeValidationVisitor when
// traversing group sequences
$cascadedGroups = count($cascadedGroups) > 0
? $cascadedGroups
: $groups;
$cascadingStrategy = $metadata->getCascadingStrategy(); $cascadingStrategy = $metadata->getCascadingStrategy();
// Quit unless we have an array or a cascaded object
if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) {
return;
}
// 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) {
@ -460,6 +456,12 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION); | ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
} }
// The "cascadedGroups" property is set by the NodeValidationVisitor when
// traversing group sequences
$cascadedGroups = count($cascadedGroups) > 0
? $cascadedGroups
: $groups;
if (is_array($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
@ -475,21 +477,17 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
return; return;
} }
if ($cascadingStrategy & CascadingStrategy::CASCADE) { // If the value is a scalar, pass it anyway, because we want
// If the value is a scalar, pass it anyway, because we want // 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( $value,
$value, $valueHash,
$valueHash, $propertyPath,
$propertyPath, $cascadedGroups,
$cascadedGroups, $traversalStrategy,
$traversalStrategy, $context
$context );
);
return;
}
// Currently, the traversal strategy can only be TRAVERSE for a // Currently, the traversal strategy can only be TRAVERSE for a
// generic node if the cascading strategy is CASCADE. Thus, traversable // generic node if the cascading strategy is CASCADE. Thus, traversable
@ -590,6 +588,8 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
// simply continue traversal (if possible) // simply continue traversal (if possible)
foreach ($groups as $key => $group) { foreach ($groups as $key => $group) {
$cascadedGroup = null;
// 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().
@ -608,11 +608,36 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
} }
$context->markObjectAsValidatedForGroup($valueHash, $groupHash); $context->markObjectAsValidatedForGroup($valueHash, $groupHash);
// Replace the "Default" group by the group sequence defined
// for the class, if applicable
// This is done after checking the cache, so that
// spl_object_hash() isn't called for this sequence and
// "Default" is used instead in the cache. This is useful
// if the getters below return different group sequences in
// every call.
if (Constraint::DEFAULT_GROUP === $group) {
if ($metadata->hasGroupSequence()) {
// The group sequence is statically defined for the class
$group = $metadata->getGroupSequence();
$cascadedGroup = Constraint::DEFAULT_GROUP;
} elseif ($metadata->isGroupSequenceProvider()) {
// The group sequence is dynamically obtained from the validated
// object
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
$group = $value->getGroupSequence();
$cascadedGroup = Constraint::DEFAULT_GROUP;
if (!$group instanceof GroupSequence) {
$group = new GroupSequence($group);
}
}
}
} }
if ($group instanceof GroupSequence) { if ($group instanceof GroupSequence) {
// Traverse group sequence until a violation is generated // Traverse group sequence until a violation is generated
$this->stepThroughGroupSequence($value, $valueHash, $container, $containerHash, $metadata, $propertyPath, $traversalStrategy, $group, $context); $this->stepThroughGroupSequence($value, $valueHash, $container, $containerHash, $metadata, $propertyPath, $traversalStrategy, $group, $cascadedGroup, $context);
// Skip the group sequence when validating successor nodes // Skip the group sequence when validating successor nodes
unset($groups[$key]); unset($groups[$key]);
@ -637,17 +662,13 @@ 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($value, $valueHash, $container, $containerHash, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, ExecutionContextInterface $context) private function stepThroughGroupSequence($value, $valueHash, $container, $containerHash, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
{ {
$violationCount = count($context->getViolations()); $violationCount = count($context->getViolations());
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
foreach ($groupSequence->groups as $groupInSequence) { foreach ($groupSequence->groups as $groupInSequence) {
$groups = array($groupInSequence); $groups = array($groupInSequence);
$cascadedGroups = null;
if (null !== $groupSequence->cascadedGroup) {
$cascadedGroups = array($groupSequence->cascadedGroup);
}
if ($metadata instanceof ClassMetadataInterface) { if ($metadata instanceof ClassMetadataInterface) {
$this->traverseClassNode( $this->traverseClassNode(
@ -727,44 +748,4 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
$validator->validate($value, $constraint); $validator->validate($value, $constraint);
} }
} }
/**
* @param $value
* @param ClassMetadataInterface $metadata
* @param array $groups
*
* @return array
*/
private function replaceDefaultGroup($value, ClassMetadataInterface $metadata, array $groups)
{
$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;
}
} }