[Validator] Improved performance of cache key generation
This commit is contained in:
parent
029a71638e
commit
3183aed7cd
@ -104,7 +104,7 @@ class ExecutionContext implements ExecutionContextInterface
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $validatedClassConstraints = array();
|
private $validatedConstraints = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores which property constraint has been validated for which property.
|
* Stores which property constraint has been validated for which property.
|
||||||
@ -319,64 +319,36 @@ class ExecutionContext implements ExecutionContextInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function markObjectAsValidatedForGroup($objectHash, $groupHash)
|
public function markGroupAsValidated($cacheKey, $groupHash)
|
||||||
{
|
{
|
||||||
if (!isset($this->validatedObjects[$objectHash])) {
|
if (!isset($this->validatedObjects[$cacheKey])) {
|
||||||
$this->validatedObjects[$objectHash] = array();
|
$this->validatedObjects[$cacheKey] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->validatedObjects[$objectHash][$groupHash] = true;
|
$this->validatedObjects[$cacheKey][$groupHash] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isObjectValidatedForGroup($objectHash, $groupHash)
|
public function isGroupValidated($cacheKey, $groupHash)
|
||||||
{
|
{
|
||||||
return isset($this->validatedObjects[$objectHash][$groupHash]);
|
return isset($this->validatedObjects[$cacheKey][$groupHash]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function markClassConstraintAsValidated($objectHash, $constraintHash)
|
public function markConstraintAsValidated($cacheKey, $constraintHash)
|
||||||
{
|
{
|
||||||
if (!isset($this->validatedClassConstraints[$objectHash])) {
|
$this->validatedConstraints[$cacheKey.':'.$constraintHash] = true;
|
||||||
$this->validatedClassConstraints[$objectHash] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->validatedClassConstraints[$objectHash][$constraintHash] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function isClassConstraintValidated($objectHash, $constraintHash)
|
public function isConstraintValidated($cacheKey, $constraintHash)
|
||||||
{
|
{
|
||||||
return isset($this->validatedClassConstraints[$objectHash][$constraintHash]);
|
return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function markPropertyConstraintAsValidated($objectHash, $propertyName, $constraintHash)
|
|
||||||
{
|
|
||||||
if (!isset($this->validatedPropertyConstraints[$objectHash])) {
|
|
||||||
$this->validatedPropertyConstraints[$objectHash] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($this->validatedPropertyConstraints[$objectHash][$propertyName])) {
|
|
||||||
$this->validatedPropertyConstraints[$objectHash][$propertyName] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->validatedPropertyConstraints[$objectHash][$propertyName][$constraintHash] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash)
|
|
||||||
{
|
|
||||||
return isset($this->validatedPropertyConstraints[$objectHash][$propertyName][$constraintHash]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,19 +124,19 @@ interface ExecutionContextInterface extends LegacyExecutionContextInterface
|
|||||||
/**
|
/**
|
||||||
* Marks an object as validated in a specific validation group.
|
* Marks an object as validated in a specific validation group.
|
||||||
*
|
*
|
||||||
* @param string $objectHash The hash of the object
|
* @param string $cacheKey The hash of the object
|
||||||
* @param string $groupHash The group's name or hash, if it is group
|
* @param string $groupHash The group's name or hash, if it is group
|
||||||
* sequence
|
* sequence
|
||||||
*
|
*
|
||||||
* @internal Used by the validator engine. Should not be called by user
|
* @internal Used by the validator engine. Should not be called by user
|
||||||
* code.
|
* code.
|
||||||
*/
|
*/
|
||||||
public function markObjectAsValidatedForGroup($objectHash, $groupHash);
|
public function markGroupAsValidated($cacheKey, $groupHash);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether an object was validated in a specific validation group.
|
* Returns whether an object was validated in a specific validation group.
|
||||||
*
|
*
|
||||||
* @param string $objectHash The hash of the object
|
* @param string $cacheKey The hash of the object
|
||||||
* @param string $groupHash The group's name or hash, if it is group
|
* @param string $groupHash The group's name or hash, if it is group
|
||||||
* sequence
|
* sequence
|
||||||
*
|
*
|
||||||
@ -146,23 +146,23 @@ interface ExecutionContextInterface extends LegacyExecutionContextInterface
|
|||||||
* @internal Used by the validator engine. Should not be called by user
|
* @internal Used by the validator engine. Should not be called by user
|
||||||
* code.
|
* code.
|
||||||
*/
|
*/
|
||||||
public function isObjectValidatedForGroup($objectHash, $groupHash);
|
public function isGroupValidated($cacheKey, $groupHash);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a constraint as validated for an object.
|
* Marks a constraint as validated for an object.
|
||||||
*
|
*
|
||||||
* @param string $objectHash The hash of the object
|
* @param string $cacheKey The hash of the object
|
||||||
* @param string $constraintHash The hash of the constraint
|
* @param string $constraintHash The hash of the constraint
|
||||||
*
|
*
|
||||||
* @internal Used by the validator engine. Should not be called by user
|
* @internal Used by the validator engine. Should not be called by user
|
||||||
* code.
|
* code.
|
||||||
*/
|
*/
|
||||||
public function markClassConstraintAsValidated($objectHash, $constraintHash);
|
public function markConstraintAsValidated($cacheKey, $constraintHash);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a constraint was validated for an object.
|
* Returns whether a constraint was validated for an object.
|
||||||
*
|
*
|
||||||
* @param string $objectHash The hash of the object
|
* @param string $cacheKey The hash of the object
|
||||||
* @param string $constraintHash The hash of the constraint
|
* @param string $constraintHash The hash of the constraint
|
||||||
*
|
*
|
||||||
* @return Boolean Whether the constraint was already validated
|
* @return Boolean Whether the constraint was already validated
|
||||||
@ -170,32 +170,5 @@ interface ExecutionContextInterface extends LegacyExecutionContextInterface
|
|||||||
* @internal Used by the validator engine. Should not be called by user
|
* @internal Used by the validator engine. Should not be called by user
|
||||||
* code.
|
* code.
|
||||||
*/
|
*/
|
||||||
public function isClassConstraintValidated($objectHash, $constraintHash);
|
public function isConstraintValidated($cacheKey, $constraintHash);
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks a constraint as validated for an object and a property name.
|
|
||||||
*
|
|
||||||
* @param string $objectHash The hash of the object
|
|
||||||
* @param string $propertyName The property name
|
|
||||||
* @param string $constraintHash The hash of the constraint
|
|
||||||
*
|
|
||||||
* @internal Used by the validator engine. Should not be called by user
|
|
||||||
* code.
|
|
||||||
*/
|
|
||||||
public function markPropertyConstraintAsValidated($objectHash, $propertyName, $constraintHash);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a constraint was validated for an object and a property
|
|
||||||
* name.
|
|
||||||
*
|
|
||||||
* @param string $objectHash The hash of the object
|
|
||||||
* @param string $propertyName The property name
|
|
||||||
* @param string $constraintHash The hash of the constraint
|
|
||||||
*
|
|
||||||
* @return Boolean Whether the constraint was already validated
|
|
||||||
*
|
|
||||||
* @internal Used by the validator engine. Should not be called by user
|
|
||||||
* code.
|
|
||||||
*/
|
|
||||||
public function isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash);
|
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class ClassNode extends Node
|
|||||||
*
|
*
|
||||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||||
*/
|
*/
|
||||||
public function __construct($object, ClassMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
public function __construct($object, $cacheKey, ClassMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||||
{
|
{
|
||||||
if (!is_object($object)) {
|
if (!is_object($object)) {
|
||||||
throw new UnexpectedTypeException($object, 'object');
|
throw new UnexpectedTypeException($object, 'object');
|
||||||
@ -63,12 +63,12 @@ class ClassNode extends Node
|
|||||||
|
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$object,
|
$object,
|
||||||
|
$cacheKey,
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
$cascadedGroups
|
$cascadedGroups,
|
||||||
|
$traversalStrategy
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->traversalStrategy = $traversalStrategy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ class CollectionNode extends Node
|
|||||||
parent::__construct(
|
parent::__construct(
|
||||||
$collection,
|
$collection,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
$cascadedGroups,
|
$cascadedGroups,
|
||||||
|
@ -30,6 +30,8 @@ abstract class Node
|
|||||||
*/
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
|
|
||||||
|
public $cacheKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata specifying how the value should be validated.
|
* The metadata specifying how the value should be validated.
|
||||||
*
|
*
|
||||||
@ -82,13 +84,14 @@ abstract class Node
|
|||||||
*
|
*
|
||||||
* @throws UnexpectedTypeException If $cascadedGroups is invalid
|
* @throws UnexpectedTypeException If $cascadedGroups is invalid
|
||||||
*/
|
*/
|
||||||
public function __construct($value, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
public function __construct($value, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||||
{
|
{
|
||||||
if (null !== $cascadedGroups && !is_array($cascadedGroups)) {
|
if (null !== $cascadedGroups && !is_array($cascadedGroups)) {
|
||||||
throw new UnexpectedTypeException($cascadedGroups, 'null or array');
|
throw new UnexpectedTypeException($cascadedGroups, 'null or array');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
|
$this->cacheKey = $cacheKey;
|
||||||
$this->metadata = $metadata;
|
$this->metadata = $metadata;
|
||||||
$this->propertyPath = $propertyPath;
|
$this->propertyPath = $propertyPath;
|
||||||
$this->groups = $groups;
|
$this->groups = $groups;
|
||||||
|
@ -41,11 +41,6 @@ use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
|||||||
*/
|
*/
|
||||||
class PropertyNode extends Node
|
class PropertyNode extends Node
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
public $object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var PropertyMetadataInterface
|
* @var PropertyMetadataInterface
|
||||||
*/
|
*/
|
||||||
@ -71,22 +66,17 @@ class PropertyNode extends Node
|
|||||||
*
|
*
|
||||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||||
*/
|
*/
|
||||||
public function __construct($object, $value, PropertyMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
public function __construct($value, $cacheKey, PropertyMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||||
{
|
{
|
||||||
if (!is_object($object)) {
|
|
||||||
throw new UnexpectedTypeException($object, 'object');
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$value,
|
$value,
|
||||||
|
$cacheKey,
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
$cascadedGroups,
|
$cascadedGroups,
|
||||||
$traversalStrategy
|
$traversalStrategy
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->object = $object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -274,8 +274,8 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$nodeStack->push(new PropertyNode(
|
$nodeStack->push(new PropertyNode(
|
||||||
$node->value,
|
|
||||||
$propertyMetadata->getPropertyValue($node->value),
|
$propertyMetadata->getPropertyValue($node->value),
|
||||||
|
$node->cacheKey.':'.$propertyName,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
$node->propertyPath
|
$node->propertyPath
|
||||||
? $node->propertyPath.'.'.$propertyName
|
? $node->propertyPath.'.'.$propertyName
|
||||||
@ -530,6 +530,7 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||||||
|
|
||||||
$nodeStack->push(new ClassNode(
|
$nodeStack->push(new ClassNode(
|
||||||
$object,
|
$object,
|
||||||
|
spl_object_hash($object),
|
||||||
$classMetadata,
|
$classMetadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
|
@ -71,14 +71,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) {
|
|
||||||
$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
|
||||||
// if necessary (G1, G2)
|
// if necessary (G1, G2)
|
||||||
@ -97,7 +89,7 @@ class NodeValidationVisitor extends AbstractVisitor
|
|||||||
// 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->isGroupValidated($node->cacheKey, $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($node->groups[$key]);
|
||||||
@ -105,7 +97,7 @@ class NodeValidationVisitor extends AbstractVisitor
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->markObjectAsValidatedForGroup($objectHash, $groupHash);
|
$context->markGroupAsValidated($node->cacheKey, $groupHash);
|
||||||
|
|
||||||
// Replace the "Default" group by the group sequence defined
|
// Replace the "Default" group by the group sequence defined
|
||||||
// for the class, if applicable
|
// for the class, if applicable
|
||||||
@ -144,7 +136,7 @@ class NodeValidationVisitor extends AbstractVisitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate normal group
|
// Validate normal group
|
||||||
$this->validateNodeForGroup($node, $group, $context, $objectHash);
|
$this->validateInGroup($node, $group, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -190,31 +182,21 @@ class NodeValidationVisitor extends AbstractVisitor
|
|||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function validateNodeForGroup(Node $node, $group, ExecutionContextInterface $context, $objectHash)
|
private function validateInGroup(Node $node, $group, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$context->setGroup($group);
|
$context->setGroup($group);
|
||||||
|
|
||||||
foreach ($node->metadata->findConstraints($group) as $constraint) {
|
foreach ($node->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 !== $node->cacheKey) {
|
||||||
$constraintHash = spl_object_hash($constraint);
|
$constraintHash = spl_object_hash($constraint);
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
if ($context->isConstraintValidated($node->cacheKey, $constraintHash)) {
|
||||||
if ($context->isClassConstraintValidated($objectHash, $constraintHash)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->markClassConstraintAsValidated($objectHash, $constraintHash);
|
$context->markConstraintAsValidated($node->cacheKey, $constraintHash);
|
||||||
} elseif ($node instanceof PropertyNode) {
|
|
||||||
$propertyName = $node->metadata->getPropertyName();
|
|
||||||
|
|
||||||
if ($context->isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$context->markPropertyConstraintAsValidated($objectHash, $propertyName, $constraintHash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$validator = $this->validatorFactory->getInstance($constraint);
|
$validator = $this->validatorFactory->getInstance($constraint);
|
||||||
|
@ -26,6 +26,6 @@ class ClassNodeTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$metadata = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataInterface');
|
$metadata = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataInterface');
|
||||||
|
|
||||||
new ClassNode('foobar', $metadata, '', array(), array());
|
new ClassNode('foobar', null, $metadata, '', array(), array());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class NonRecursiveNodeTraverserTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testVisitorsMayPreventTraversal()
|
public function testVisitorsMayPreventTraversal()
|
||||||
{
|
{
|
||||||
$nodes = array(new GenericNode('value', new GenericMetadata(), '', array('Default')));
|
$nodes = array(new GenericNode('value', null, new GenericMetadata(), '', array('Default')));
|
||||||
$context = $this->getMock('Symfony\Component\Validator\Context\ExecutionContextInterface');
|
$context = $this->getMock('Symfony\Component\Validator\Context\ExecutionContextInterface');
|
||||||
|
|
||||||
$visitor1 = $this->getMock('Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface');
|
$visitor1 = $this->getMock('Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface');
|
||||||
|
@ -100,11 +100,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$metadata = new GenericMetadata();
|
$metadata = new GenericMetadata();
|
||||||
$metadata->addConstraints($constraints);
|
$metadata->addConstraints($constraints);
|
||||||
|
|
||||||
$this->traverseGenericNode(
|
$this->validateGenericNode(
|
||||||
$value,
|
$value,
|
||||||
is_object($value) ? spl_object_hash($value) : null,
|
is_object($value) ? spl_object_hash($value) : null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$metadata,
|
$metadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
@ -119,7 +117,6 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
if (is_object($value)) {
|
if (is_object($value)) {
|
||||||
$this->cascadeObject(
|
$this->cascadeObject(
|
||||||
$value,
|
$value,
|
||||||
spl_object_hash($value),
|
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
TraversalStrategy::IMPLICIT,
|
TraversalStrategy::IMPLICIT,
|
||||||
@ -168,16 +165,14 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
$containerHash = spl_object_hash($container);
|
$cacheKey = spl_object_hash($container);
|
||||||
|
|
||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$propertyValue = $propertyMetadata->getPropertyValue($container);
|
$propertyValue = $propertyMetadata->getPropertyValue($container);
|
||||||
|
|
||||||
$this->traverseGenericNode(
|
$this->validateGenericNode(
|
||||||
$propertyValue,
|
$propertyValue,
|
||||||
is_object($propertyValue) ? spl_object_hash($propertyValue) : null,
|
$cacheKey.':'.$propertyName,
|
||||||
$container,
|
|
||||||
$containerHash,
|
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups,
|
$groups,
|
||||||
@ -210,14 +205,12 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
$containerHash = spl_object_hash($container);
|
$cacheKey = spl_object_hash($container);
|
||||||
|
|
||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$this->traverseGenericNode(
|
$this->validateGenericNode(
|
||||||
$value,
|
$value,
|
||||||
is_object($value) ? spl_object_hash($value) : null,
|
$cacheKey.':'.$propertyName,
|
||||||
$container,
|
|
||||||
$containerHash,
|
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups,
|
$groups,
|
||||||
@ -275,9 +268,74 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
* @see CollectionNode
|
* @see CollectionNode
|
||||||
* @see TraversalStrategy
|
* @see TraversalStrategy
|
||||||
*/
|
*/
|
||||||
private function traverseClassNode($value, $valueHash, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
private function validateClassNode($value, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$groups = $this->validateNode($value, $valueHash, null, null, $metadata, $propertyPath, $groups, $traversalStrategy, $context);
|
$context->setNode($value, $metadata, $propertyPath);
|
||||||
|
|
||||||
|
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
||||||
|
// then call traverse() with each entry of the group sequence and abort
|
||||||
|
// if necessary (G1, G2)
|
||||||
|
// finally call traverse() with remaining entries ([G3,G4]) or
|
||||||
|
// 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().
|
||||||
|
// The following shortcut, however, prevents validatedNodeForGroup()
|
||||||
|
// from being called at all and enhances performance a bit.
|
||||||
|
|
||||||
|
// Use the object hash for group sequences
|
||||||
|
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
||||||
|
|
||||||
|
if ($context->isGroupValidated($cacheKey, $groupHash)) {
|
||||||
|
// Skip this group when validating the successor nodes
|
||||||
|
// (property and/or collection nodes)
|
||||||
|
unset($groups[$key]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context->markGroupAsValidated($cacheKey, $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) {
|
||||||
|
$this->stepThroughGroupSequence($value, $cacheKey, $metadata, $propertyPath, $traversalStrategy, $group, $cascadedGroup, $context);
|
||||||
|
|
||||||
|
// Skip the group sequence when validating successor nodes
|
||||||
|
unset($groups[$key]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
|
||||||
|
}
|
||||||
|
|
||||||
if (0 === count($groups)) {
|
if (0 === count($groups)) {
|
||||||
return;
|
return;
|
||||||
@ -296,11 +354,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyValue = $propertyMetadata->getPropertyValue($value);
|
$propertyValue = $propertyMetadata->getPropertyValue($value);
|
||||||
|
|
||||||
$this->traverseGenericNode(
|
$this->validateGenericNode(
|
||||||
$propertyValue,
|
$propertyValue,
|
||||||
is_object($propertyValue) ? spl_object_hash($propertyValue) : null,
|
$cacheKey.':'.$propertyName,
|
||||||
$value,
|
|
||||||
$valueHash,
|
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
$propertyPath
|
$propertyPath
|
||||||
? $propertyPath.'.'.$propertyName
|
? $propertyPath.'.'.$propertyName
|
||||||
@ -351,6 +407,171 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses a node that is neither a class nor a collection node.
|
||||||
|
*
|
||||||
|
* At first, each visitor is invoked for this node. Then, unless any
|
||||||
|
* of the visitors aborts the traversal by returning false, the successor
|
||||||
|
* nodes of the collection node are put on the stack:
|
||||||
|
*
|
||||||
|
* - if the node contains an object with associated class metadata, a new
|
||||||
|
* class node is put on the stack;
|
||||||
|
* - if the node contains a traversable object without associated class
|
||||||
|
* metadata and traversal is enabled according to the selected traversal
|
||||||
|
* strategy, a collection node is put on the stack;
|
||||||
|
* - if the node contains an array, a collection node is put on the stack.
|
||||||
|
*
|
||||||
|
* @param Node $node The node
|
||||||
|
* @param ExecutionContextInterface $context The current execution context
|
||||||
|
*/
|
||||||
|
private function validateGenericNode($value, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
|
{
|
||||||
|
$context->setNode($value, $metadata, $propertyPath);
|
||||||
|
|
||||||
|
foreach ($groups as $key => $group) {
|
||||||
|
if ($group instanceof GroupSequence) {
|
||||||
|
$this->stepThroughGroupSequence($value, $cacheKey, $metadata, $propertyPath, $traversalStrategy, $group, null, $context);
|
||||||
|
|
||||||
|
// Skip the group sequence when validating successor nodes
|
||||||
|
unset($groups[$key]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === count($groups)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
// Keep the STOP_RECURSION flag, if it was set
|
||||||
|
$traversalStrategy = $metadata->getTraversalStrategy()
|
||||||
|
| ($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
|
||||||
|
// (BC with Symfony < 2.5)
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$value,
|
||||||
|
$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,
|
||||||
|
$propertyPath,
|
||||||
|
$cascadedGroups,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
|
||||||
|
// Currently, the traversal strategy can only be TRAVERSE for a
|
||||||
|
// generic node if the cascading strategy is CASCADE. Thus, traversable
|
||||||
|
// objects will always be handled within cascadeObject() and there's
|
||||||
|
// nothing more to do here.
|
||||||
|
|
||||||
|
// see GenericMetadata::addConstraint()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the cascading logic for an object.
|
||||||
|
*
|
||||||
|
* If class metadata is available for the object, a class node is put on
|
||||||
|
* the node stack. Otherwise, if the selected traversal strategy allows
|
||||||
|
* traversal of the object, a new collection node is put on the stack.
|
||||||
|
* Otherwise, an exception is thrown.
|
||||||
|
*
|
||||||
|
* @param object $container The object to cascade
|
||||||
|
* @param string $propertyPath The current property path
|
||||||
|
* @param string[] $groups The validated groups
|
||||||
|
* @param integer $traversalStrategy The strategy for traversing the
|
||||||
|
* cascaded object
|
||||||
|
* @param ExecutionContextInterface $context The current execution context
|
||||||
|
*
|
||||||
|
* @throws NoSuchMetadataException If the object has no associated metadata
|
||||||
|
* and does not implement {@link \Traversable}
|
||||||
|
* or if traversal is disabled via the
|
||||||
|
* $traversalStrategy argument
|
||||||
|
* @throws UnsupportedMetadataException If the metadata returned by the
|
||||||
|
* metadata factory does not implement
|
||||||
|
* {@link ClassMetadataInterface}
|
||||||
|
*/
|
||||||
|
private function cascadeObject($container, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$classMetadata = $this->metadataFactory->getMetadataFor($container);
|
||||||
|
|
||||||
|
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||||
|
throw new UnsupportedMetadataException(sprintf(
|
||||||
|
'The metadata factory should return instances of '.
|
||||||
|
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||||
|
'got: "%s".',
|
||||||
|
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validateClassNode(
|
||||||
|
$container,
|
||||||
|
spl_object_hash($container),
|
||||||
|
$classMetadata,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
null,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
} catch (NoSuchMetadataException $e) {
|
||||||
|
// Rethrow if not Traversable
|
||||||
|
if (!$container instanceof \Traversable) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rethrow unless IMPLICIT or TRAVERSE
|
||||||
|
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$container,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$traversalStrategy,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traverses a collection node.
|
* Traverses a collection node.
|
||||||
*
|
*
|
||||||
@ -402,7 +623,6 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
if (is_object($value)) {
|
if (is_object($value)) {
|
||||||
$this->cascadeObject(
|
$this->cascadeObject(
|
||||||
$value,
|
$value,
|
||||||
spl_object_hash($value),
|
|
||||||
$propertyPath.'['.$key.']',
|
$propertyPath.'['.$key.']',
|
||||||
$groups,
|
$groups,
|
||||||
$traversalStrategy,
|
$traversalStrategy,
|
||||||
@ -412,246 +632,6 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Traverses a node that is neither a class nor a collection node.
|
|
||||||
*
|
|
||||||
* At first, each visitor is invoked for this node. Then, unless any
|
|
||||||
* of the visitors aborts the traversal by returning false, the successor
|
|
||||||
* nodes of the collection node are put on the stack:
|
|
||||||
*
|
|
||||||
* - if the node contains an object with associated class metadata, a new
|
|
||||||
* class node is put on the stack;
|
|
||||||
* - if the node contains a traversable object without associated class
|
|
||||||
* metadata and traversal is enabled according to the selected traversal
|
|
||||||
* strategy, a collection node is put on the stack;
|
|
||||||
* - if the node contains an array, a collection node is put on the stack.
|
|
||||||
*
|
|
||||||
* @param Node $node The node
|
|
||||||
* @param ExecutionContextInterface $context The current execution context
|
|
||||||
*/
|
|
||||||
private function traverseGenericNode($value, $valueHash, $container, $containerHash, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
|
|
||||||
{
|
|
||||||
$groups = $this->validateNode($value, $valueHash, $container, $containerHash, $metadata, $propertyPath, $groups, $traversalStrategy, $context);
|
|
||||||
|
|
||||||
if (0 === count($groups)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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) {
|
|
||||||
// Keep the STOP_RECURSION flag, if it was set
|
|
||||||
$traversalStrategy = $metadata->getTraversalStrategy()
|
|
||||||
| ($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
|
|
||||||
// (BC with Symfony < 2.5)
|
|
||||||
$this->cascadeCollection(
|
|
||||||
$value,
|
|
||||||
$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
|
|
||||||
// objects will always be handled within cascadeObject() and there's
|
|
||||||
// nothing more to do here.
|
|
||||||
|
|
||||||
// see GenericMetadata::addConstraint()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the cascading logic for an object.
|
|
||||||
*
|
|
||||||
* If class metadata is available for the object, a class node is put on
|
|
||||||
* the node stack. Otherwise, if the selected traversal strategy allows
|
|
||||||
* traversal of the object, a new collection node is put on the stack.
|
|
||||||
* Otherwise, an exception is thrown.
|
|
||||||
*
|
|
||||||
* @param object $container The object to cascade
|
|
||||||
* @param string $propertyPath The current property path
|
|
||||||
* @param string[] $groups The validated groups
|
|
||||||
* @param integer $traversalStrategy The strategy for traversing the
|
|
||||||
* cascaded object
|
|
||||||
* @param ExecutionContextInterface $context The current execution context
|
|
||||||
*
|
|
||||||
* @throws NoSuchMetadataException If the object has no associated metadata
|
|
||||||
* and does not implement {@link \Traversable}
|
|
||||||
* or if traversal is disabled via the
|
|
||||||
* $traversalStrategy argument
|
|
||||||
* @throws UnsupportedMetadataException If the metadata returned by the
|
|
||||||
* metadata factory does not implement
|
|
||||||
* {@link ClassMetadataInterface}
|
|
||||||
*/
|
|
||||||
private function cascadeObject($container, $containerHash, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$classMetadata = $this->metadataFactory->getMetadataFor($container);
|
|
||||||
|
|
||||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
|
||||||
throw new UnsupportedMetadataException(sprintf(
|
|
||||||
'The metadata factory should return instances of '.
|
|
||||||
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
|
||||||
'got: "%s".',
|
|
||||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->traverseClassNode(
|
|
||||||
$container,
|
|
||||||
$containerHash,
|
|
||||||
$classMetadata,
|
|
||||||
$propertyPath,
|
|
||||||
$groups,
|
|
||||||
null,
|
|
||||||
$traversalStrategy,
|
|
||||||
$context
|
|
||||||
);
|
|
||||||
} catch (NoSuchMetadataException $e) {
|
|
||||||
// Rethrow if not Traversable
|
|
||||||
if (!$container instanceof \Traversable) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rethrow unless IMPLICIT or TRAVERSE
|
|
||||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cascadeCollection(
|
|
||||||
$container,
|
|
||||||
$propertyPath,
|
|
||||||
$groups,
|
|
||||||
$traversalStrategy,
|
|
||||||
$context
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a node's value against the constraints defined in the node's
|
|
||||||
* metadata.
|
|
||||||
*
|
|
||||||
* Objects and constraints that were validated before in the same context
|
|
||||||
* will be skipped.
|
|
||||||
*
|
|
||||||
* @param Node $node The current node
|
|
||||||
* @param ExecutionContextInterface $context The execution context
|
|
||||||
*
|
|
||||||
* @return array The groups in which the successor nodes should be validated
|
|
||||||
*/
|
|
||||||
public function validateNode($value, $valueHash, $container, $containerHash, MetadataInterface $metadata = null, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
|
||||||
{
|
|
||||||
$context->setNode($value, $metadata, $propertyPath);
|
|
||||||
|
|
||||||
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
|
||||||
// then call traverse() with each entry of the group sequence and abort
|
|
||||||
// if necessary (G1, G2)
|
|
||||||
// finally call traverse() with remaining entries ([G3,G4]) or
|
|
||||||
// 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().
|
|
||||||
// The following shortcut, however, prevents validatedNodeForGroup()
|
|
||||||
// from being called at all and enhances performance a bit.
|
|
||||||
if ($metadata instanceof ClassMetadataInterface) {
|
|
||||||
// Use the object hash for group sequences
|
|
||||||
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
|
||||||
|
|
||||||
if ($context->isObjectValidatedForGroup($valueHash, $groupHash)) {
|
|
||||||
// Skip this group when validating the successor nodes
|
|
||||||
// (property and/or collection nodes)
|
|
||||||
unset($groups[$key]);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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, $cascadedGroup, $context);
|
|
||||||
|
|
||||||
// Skip the group sequence when validating successor nodes
|
|
||||||
unset($groups[$key]);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate normal group
|
|
||||||
$this->validateNodeForGroup($value, $valueHash, $containerHash, $metadata, $group, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a node's value in each group of a group sequence.
|
* Validates a node's value in each group of a group sequence.
|
||||||
*
|
*
|
||||||
@ -662,7 +642,7 @@ 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, $cascadedGroup, ExecutionContextInterface $context)
|
private function stepThroughGroupSequence($value, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$violationCount = count($context->getViolations());
|
$violationCount = count($context->getViolations());
|
||||||
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
|
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
|
||||||
@ -671,9 +651,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$groups = array($groupInSequence);
|
$groups = array($groupInSequence);
|
||||||
|
|
||||||
if ($metadata instanceof ClassMetadataInterface) {
|
if ($metadata instanceof ClassMetadataInterface) {
|
||||||
$this->traverseClassNode(
|
$this->validateClassNode(
|
||||||
$value,
|
$value,
|
||||||
$valueHash,
|
$cacheKey,
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
@ -682,11 +662,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$context
|
$context
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->traverseGenericNode(
|
$this->validateGenericNode(
|
||||||
$value,
|
$value,
|
||||||
$valueHash,
|
$cacheKey,
|
||||||
$container,
|
|
||||||
$containerHash,
|
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
@ -714,33 +692,21 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function validateNodeForGroup($value, $valueHash, $containerHash, MetadataInterface $metadata = null, $group, ExecutionContextInterface $context)
|
private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, $group, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
$context->setGroup($group);
|
$context->setGroup($group);
|
||||||
|
|
||||||
$propertyName = $metadata instanceof PropertyMetadataInterface
|
|
||||||
? $metadata->getPropertyName()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
foreach ($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 !== $propertyName) {
|
if (null !== $cacheKey) {
|
||||||
$constraintHash = spl_object_hash($constraint);
|
$constraintHash = spl_object_hash($constraint);
|
||||||
|
|
||||||
if ($context->isPropertyConstraintValidated($containerHash, $propertyName, $constraintHash)) {
|
if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context->markPropertyConstraintAsValidated($containerHash, $propertyName, $constraintHash);
|
$context->markConstraintAsValidated($cacheKey, $constraintHash);
|
||||||
} elseif (null !== $valueHash) {
|
|
||||||
$constraintHash = spl_object_hash($constraint);
|
|
||||||
|
|
||||||
if ($context->isClassConstraintValidated($valueHash, $constraintHash)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$context->markClassConstraintAsValidated($valueHash, $constraintHash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$validator = $this->validatorFactory->getInstance($constraint);
|
$validator = $this->validatorFactory->getInstance($constraint);
|
||||||
|
@ -94,6 +94,7 @@ class TraversingContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$node = new GenericNode(
|
$node = new GenericNode(
|
||||||
$value,
|
$value,
|
||||||
|
is_object($value) ? spl_object_hash($value) : null,
|
||||||
$metadata,
|
$metadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups
|
$groups
|
||||||
@ -118,6 +119,7 @@ class TraversingContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$node = new ClassNode(
|
$node = new ClassNode(
|
||||||
$value,
|
$value,
|
||||||
|
spl_object_hash($value),
|
||||||
$metadata,
|
$metadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups
|
$groups
|
||||||
@ -155,14 +157,15 @@ class TraversingContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
$cacheKey = spl_object_hash($object);
|
||||||
$nodes = array();
|
$nodes = array();
|
||||||
|
|
||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
||||||
|
|
||||||
$nodes[] = new PropertyNode(
|
$nodes[] = new PropertyNode(
|
||||||
$object,
|
|
||||||
$propertyValue,
|
$propertyValue,
|
||||||
|
$cacheKey.':'.$propertyName,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups
|
$groups
|
||||||
@ -194,12 +197,13 @@ class TraversingContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
$cacheKey = spl_object_hash($object);
|
||||||
$nodes = array();
|
$nodes = array();
|
||||||
|
|
||||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||||
$nodes[] = new PropertyNode(
|
$nodes[] = new PropertyNode(
|
||||||
$object,
|
|
||||||
$value,
|
$value,
|
||||||
|
$cacheKey.':'.$propertyName,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
$groups,
|
$groups,
|
||||||
|
Reference in New Issue
Block a user