diff --git a/src/Symfony/Component/Validator/Constraints/Valid.php b/src/Symfony/Component/Validator/Constraints/Valid.php index 19178b9003..b6485be462 100644 --- a/src/Symfony/Component/Validator/Constraints/Valid.php +++ b/src/Symfony/Component/Validator/Constraints/Valid.php @@ -15,19 +15,7 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; class Valid extends \Symfony\Component\Validator\Constraint { - /** - * This constraint does not accept any options - * - * @param mixed $options Unsupported argument! - * - * @throws InvalidOptionsException When the parameter $options is not NULL - */ - public function __construct($options = null) - { - if (null !== $options && count($options) > 0) { - throw new ConstraintDefinitionException('The constraint Valid does not accept any options'); - } - } + public $traverse = true; /** * {@inheritDoc} diff --git a/src/Symfony/Component/Validator/GraphWalker.php b/src/Symfony/Component/Validator/GraphWalker.php index edaab75d9e..83068b01a8 100644 --- a/src/Symfony/Component/Validator/GraphWalker.php +++ b/src/Symfony/Component/Validator/GraphWalker.php @@ -129,20 +129,24 @@ class GraphWalker } if ($metadata->isCascaded()) { - $this->walkReference($value, $propagatedGroup ?: $group, $propertyPath); + $this->walkReference($value, $propagatedGroup ?: $group, $propertyPath, $metadata->isCollectionCascaded()); } } - protected function walkReference($value, $group, $propertyPath) + protected function walkReference($value, $group, $propertyPath, $traverse) { if (null !== $value) { - if (is_array($value)) { - foreach ($value as $key => $element) { - $this->walkReference($element, $group, $propertyPath.'['.$key.']'); - } - } else if (!is_object($value)) { + if (!is_object($value) && !is_array($value)) { throw new UnexpectedTypeException($value, 'object or array'); - } else { + } + + if ($traverse && (is_array($value) || $value instanceof \Traversable)) { + foreach ($value as $key => $element) { + $this->walkReference($element, $group, $propertyPath.'['.$key.']', $traverse); + } + } + + if (is_object($value)) { $metadata = $this->metadataFactory->getClassMetadata(get_class($value)); $this->walkObject($metadata, $value, $group, $propertyPath); } diff --git a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php index 0e62e3bdfc..ba187745ed 100644 --- a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php @@ -22,6 +22,7 @@ abstract class MemberMetadata extends ElementMetadata public $name; public $property; public $cascaded = false; + public $collectionCascaded = false; private $reflMember; /** @@ -52,6 +53,7 @@ abstract class MemberMetadata extends ElementMetadata if ($constraint instanceof Valid) { $this->cascaded = true; + $this->collectionCascaded = $constraint->traverse; } else { parent::addConstraint($constraint); } @@ -144,6 +146,17 @@ abstract class MemberMetadata extends ElementMetadata return $this->cascaded; } + /** + * Returns whether arrays or traversable objects stored in this member + * should be traversed and validated in each entry + * + * @return Boolean + */ + public function isCollectionCascaded() + { + return $this->collectionCascaded; + } + /** * Returns the value of this property in the given object * diff --git a/tests/Symfony/Tests/Component/Validator/GraphWalkerTest.php b/tests/Symfony/Tests/Component/Validator/GraphWalkerTest.php index 07aa057b21..2f5a8ae4ce 100644 --- a/tests/Symfony/Tests/Component/Validator/GraphWalkerTest.php +++ b/tests/Symfony/Tests/Component/Validator/GraphWalkerTest.php @@ -237,7 +237,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($violations, $this->walker->getViolations()); } - public function testWalkCascadedPropertyValidatesArrays() + public function testWalkCascadedPropertyValidatesArraysByDefault() { $entity = new Entity(); $entityMetadata = new ClassMetadata(get_class($entity)); @@ -269,6 +269,67 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($violations, $this->walker->getViolations()); } + public function testWalkCascadedPropertyValidatesTraversableByDefault() + { + $entity = new Entity(); + $entityMetadata = new ClassMetadata(get_class($entity)); + $this->factory->addClassMetadata($entityMetadata); + $this->factory->addClassMetadata(new ClassMetadata('ArrayIterator')); + + // add a constraint for the entity that always fails + $entityMetadata->addConstraint(new FailingConstraint()); + + // validate array when validating the property "reference" + $this->metadata->addPropertyConstraint('reference', new Valid()); + + $this->walker->walkPropertyValue( + $this->metadata, + 'reference', + new \ArrayIterator(array('key' => $entity)), + 'Default', + 'path' + ); + + $violations = new ConstraintViolationList(); + $violations->add(new ConstraintViolation( + '', + array(), + 'Root', + 'path[key]', + $entity + )); + + $this->assertEquals($violations, $this->walker->getViolations()); + } + + public function testWalkCascadedPropertyDoesNotValidateTraversableIfDisabled() + { + $entity = new Entity(); + $entityMetadata = new ClassMetadata(get_class($entity)); + $this->factory->addClassMetadata($entityMetadata); + $this->factory->addClassMetadata(new ClassMetadata('ArrayIterator')); + + // add a constraint for the entity that always fails + $entityMetadata->addConstraint(new FailingConstraint()); + + // validate array when validating the property "reference" + $this->metadata->addPropertyConstraint('reference', new Valid(array( + 'traverse' => false, + ))); + + $this->walker->walkPropertyValue( + $this->metadata, + 'reference', + new \ArrayIterator(array('key' => $entity)), + 'Default', + 'path' + ); + + $violations = new ConstraintViolationList(); + + $this->assertEquals($violations, $this->walker->getViolations()); + } + public function testWalkCascadedPropertyDoesNotValidateNullValues() { $this->metadata->addPropertyConstraint('reference', new Valid());