[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);
if ($node instanceof ClassNode) {
$this->replaceDefaultGroup($node);
$objectHash = spl_object_hash($node->value);
} elseif ($node instanceof PropertyNode) {
$objectHash = spl_object_hash($node->object);
@ -88,6 +86,8 @@ class NodeValidationVisitor extends AbstractVisitor
// simply continue traversal (if possible)
foreach ($node->groups as $key => $group) {
$cascadedGroup = null;
// Even if we remove the following clause, the constraints on an
// object won't be validated again due to the measures taken in
// validateNodeForGroup().
@ -106,11 +106,36 @@ class NodeValidationVisitor extends AbstractVisitor
}
$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) {
// 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
unset($node->groups[$key]);
@ -135,17 +160,15 @@ class NodeValidationVisitor extends AbstractVisitor
* @param GroupSequence $groupSequence The group sequence
* @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());
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
foreach ($groupSequence->groups as $groupInSequence) {
$node = clone $node;
$node->groups = array($groupInSequence);
if (null !== $groupSequence->cascadedGroup) {
$node->cascadedGroups = array($groupSequence->cascadedGroup);
}
$node->cascadedGroups = $cascadedGroups;
$this->nodeTraverser->traverse(array($node), $context);
@ -199,44 +222,4 @@ class NodeValidationVisitor extends AbstractVisitor
$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)
{
// 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);
if (0 === count($groups)) {
@ -444,14 +441,13 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
return;
}
// The "cascadedGroups" property is set by the NodeValidationVisitor when
// traversing group sequences
$cascadedGroups = count($cascadedGroups) > 0
? $cascadedGroups
: $groups;
$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
// was called, use the traversal strategy of the node's metadata
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
@ -460,6 +456,12 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
| ($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)) {
// Arrays are always traversed, independent of the specified
// traversal strategy
@ -475,21 +477,17 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
return;
}
if ($cascadingStrategy & CascadingStrategy::CASCADE) {
// If the value is a scalar, pass it anyway, because we want
// a NoSuchMetadataException to be thrown in that case
// (BC with Symfony < 2.5)
$this->cascadeObject(
$value,
$valueHash,
$propertyPath,
$cascadedGroups,
$traversalStrategy,
$context
);
return;
}
// If the value is a scalar, pass it anyway, because we want
// a NoSuchMetadataException to be thrown in that case
// (BC with Symfony < 2.5)
$this->cascadeObject(
$value,
$valueHash,
$propertyPath,
$cascadedGroups,
$traversalStrategy,
$context
);
// Currently, the traversal strategy can only be TRAVERSE for a
// generic node if the cascading strategy is CASCADE. Thus, traversable
@ -590,6 +588,8 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
// simply continue traversal (if possible)
foreach ($groups as $key => $group) {
$cascadedGroup = null;
// Even if we remove the following clause, the constraints on an
// object won't be validated again due to the measures taken in
// validateNodeForGroup().
@ -608,11 +608,36 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
}
$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) {
// 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
unset($groups[$key]);
@ -637,17 +662,13 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
* @param GroupSequence $groupSequence The group sequence
* @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());
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
foreach ($groupSequence->groups as $groupInSequence) {
$groups = array($groupInSequence);
$cascadedGroups = null;
if (null !== $groupSequence->cascadedGroup) {
$cascadedGroups = array($groupSequence->cascadedGroup);
}
if ($metadata instanceof ClassMetadataInterface) {
$this->traverseClassNode(
@ -727,44 +748,4 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
$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;
}
}