diff --git a/src/Symfony/Component/Validator/Constraints/Traverse.php b/src/Symfony/Component/Validator/Constraints/Traverse.php new file mode 100644 index 0000000000..d9afe60c07 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Traverse.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * + * @since 2.5 + * @author Bernhard Schussek + */ +class Traverse extends Constraint +{ + public $traverse = true; + + public $deep = false; + + public function __construct($options = null) + { + if (is_array($options) && array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf( + 'The option "groups" is not supported by the constraint %s', + __CLASS__ + )); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'traverse'; + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT); + } +} diff --git a/src/Symfony/Component/Validator/Constraints/Valid.php b/src/Symfony/Component/Validator/Constraints/Valid.php index ab4676d3df..6e84e9a5f0 100644 --- a/src/Symfony/Component/Validator/Constraints/Valid.php +++ b/src/Symfony/Component/Validator/Constraints/Valid.php @@ -21,12 +21,8 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException; * * @api */ -class Valid extends Constraint +class Valid extends Traverse { - public $traverse = true; - - public $deep = false; - public function __construct($options = null) { if (is_array($options) && array_key_exists('groups', $options)) { @@ -35,4 +31,11 @@ class Valid extends Constraint parent::__construct($options); } + + public function getDefaultOption() + { + // Traverse is extended for backwards compatibility reasons + // The parent class should be removed in 3.0 + return null; + } } diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index bc6abc39b1..5b031aabbd 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -80,6 +80,23 @@ class ExecutionContext implements ExecutionContextInterface */ private $translationDomain; + /** + * Creates a new execution context. + * + * @param mixed $root The root value of the + * validated object graph + * @param ValidatorInterface $validator The validator + * @param GroupManagerInterface $groupManager The manager for accessing + * the currently validated + * group + * @param TranslatorInterface $translator The translator + * @param string|null $translationDomain The translation domain to + * use for translating + * violation messages + * + * @internal Called by {@link ExecutionContextManager}. Should not be used + * in user code. + */ public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) { $this->root = $root; diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php index 5ff0a81724..cbbc3c7447 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -148,7 +148,7 @@ interface ExecutionContextInterface * a {@link Mapping\PropertyMetadata} instance if the current value is * the value of a property and a {@link Mapping\GetterMetadata} instance if * the validated value is the result of a getter method. The metadata can - * also be an {@link Mapping\AdHocMetadata} if the current value does not + * also be an {@link Mapping\GenericMetadata} if the current value does not * belong to any structural element. * * @return MetadataInterface|null The metadata of the currently validated diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php index 8cf17f5ee3..ad38accc62 100644 --- a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php @@ -19,11 +19,25 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; /** - * @since %%NextVersion%% + * A backwards compatible execution context. + * + * @since 2.5 * @author Bernhard Schussek + * + * @deprecated Implemented for backwards compatibility with Symfony < 2.5. To be + * removed in 3.0. */ class LegacyExecutionContext extends ExecutionContext implements LegacyExecutionContextInterface { + /** + * Creates a new context. + * + * This constructor ensures that the given validator implements the + * deprecated {@link \Symfony\Component\Validator\ValidatorInterface}. If + * it does not, an {@link InvalidArgumentException} is thrown. + * + * @see ExecutionContext::__construct() + */ public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) { if (!$validator instanceof LegacyValidatorInterface) { @@ -56,6 +70,9 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution parent::addViolation($message, $parameters); } + /** + * {@inheritdoc} + */ public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $pluralization = null, $code = null) { if (func_num_args() >= 3) { @@ -78,6 +95,9 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution ; } + /** + * {@inheritdoc} + */ public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false) { // TODO handle $traverse and $deep @@ -90,6 +110,9 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution ; } + /** + * {@inheritdoc} + */ public function validateValue($value, $constraints, $subPath = '', $groups = null) { return $this @@ -100,6 +123,9 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution ; } + /** + * {@inheritdoc} + */ public function getMetadataFactory() { return $this->getValidator()->getMetadataFactory(); diff --git a/src/Symfony/Component/Validator/Mapping/AdHocMetadata.php b/src/Symfony/Component/Validator/Mapping/AdHocMetadata.php deleted file mode 100644 index f7d7d563c3..0000000000 --- a/src/Symfony/Component/Validator/Mapping/AdHocMetadata.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Mapping; - -use Symfony\Component\Validator\Constraints\Valid; -use Symfony\Component\Validator\Exception\ValidatorException; - -/** - * @since %%NextVersion%% - * @author Bernhard Schussek - */ -class AdHocMetadata extends ElementMetadata implements MetadataInterface -{ - public function __construct(array $constraints) - { - foreach ($constraints as $constraint) { - if ($constraint instanceof Valid) { - // Why can't the Valid constraint be executed directly? - // - // It cannot be executed like regular other constraints, because regular - // constraints are only executed *if they belong to the validated group*. - // The Valid constraint, on the other hand, is always executed and propagates - // the group to the cascaded object. The propagated group depends on - // - // * Whether a group sequence is currently being executed. Then the default - // group is propagated. - // - // * Otherwise the validated group is propagated. - - throw new ValidatorException(sprintf( - 'The constraint "%s" cannot be validated. Use the method '. - 'validate() instead.', - get_class($constraint) - )); - } - - $this->addConstraint($constraint); - } - } - - public function getCascadingStrategy() - { - - } - - public function getTraversalStrategy() - { - - } -} diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index 5712122445..d3c775b579 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Mapping; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ValidationVisitorInterface; use Symfony\Component\Validator\PropertyMetadataContainerInterface; use Symfony\Component\Validator\MetadataInterface as LegacyMetadataInterface; @@ -62,8 +63,6 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface, */ public $groupSequenceProvider = false; - public $traversalStrategy = TraversalStrategy::IMPLICIT; - /** * @var \ReflectionClass */ @@ -126,7 +125,12 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface, */ public function __sleep() { - return array_merge(parent::__sleep(), array( + $parentProperties = parent::__sleep(); + + // Don't store the cascading strategy. Classes never cascade. + unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]); + + return array_merge($parentProperties, array( 'getters', 'groupSequence', 'groupSequenceProvider', @@ -174,7 +178,14 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface, { if (!in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) { throw new ConstraintDefinitionException(sprintf( - 'The constraint %s cannot be put on classes', + 'The constraint "%s" cannot be put on classes.', + get_class($constraint) + )); + } + + if ($constraint instanceof Valid) { + throw new ConstraintDefinitionException(sprintf( + 'The constraint "%s" cannot be put on classes.', get_class($constraint) )); } @@ -434,9 +445,4 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface, { return CascadingStrategy::NONE; } - - public function getTraversalStrategy() - { - return $this->traversalStrategy; - } } diff --git a/src/Symfony/Component/Validator/Mapping/ElementMetadata.php b/src/Symfony/Component/Validator/Mapping/ElementMetadata.php index 9dedb79fd9..84a826aa10 100644 --- a/src/Symfony/Component/Validator/Mapping/ElementMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ElementMetadata.php @@ -11,97 +11,6 @@ namespace Symfony\Component\Validator\Mapping; -use Symfony\Component\Validator\Constraint; - -abstract class ElementMetadata +abstract class ElementMetadata extends GenericMetadata { - /** - * @var Constraint[] - */ - public $constraints = array(); - - /** - * @var array - */ - public $constraintsByGroup = array(); - - /** - * Returns the names of the properties that should be serialized. - * - * @return array - */ - public function __sleep() - { - return array( - 'constraints', - 'constraintsByGroup', - ); - } - - /** - * Clones this object. - */ - public function __clone() - { - $constraints = $this->constraints; - - $this->constraints = array(); - $this->constraintsByGroup = array(); - - foreach ($constraints as $constraint) { - $this->addConstraint(clone $constraint); - } - } - - /** - * Adds a constraint to this element. - * - * @param Constraint $constraint - * - * @return ElementMetadata - */ - public function addConstraint(Constraint $constraint) - { - $this->constraints[] = $constraint; - - foreach ($constraint->groups as $group) { - $this->constraintsByGroup[$group][] = $constraint; - } - - return $this; - } - - /** - * Returns all constraints of this element. - * - * @return Constraint[] An array of Constraint instances - */ - public function getConstraints() - { - return $this->constraints; - } - - /** - * Returns whether this element has any constraints. - * - * @return Boolean - */ - public function hasConstraints() - { - return count($this->constraints) > 0; - } - - /** - * Returns the constraints of the given group and global ones (* group). - * - * @param string $group The group name - * - * @return array An array with all Constraint instances belonging to the group - */ - public function findConstraints($group) - { - return isset($this->constraintsByGroup[$group]) - ? $this->constraintsByGroup[$group] - : array(); - } } diff --git a/src/Symfony/Component/Validator/Mapping/GenericMetadata.php b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php new file mode 100644 index 0000000000..75b07c8825 --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Traverse; +use Symfony\Component\Validator\Constraints\Valid; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class GenericMetadata implements MetadataInterface +{ + /** + * @var Constraint[] + */ + public $constraints = array(); + + /** + * @var array + */ + public $constraintsByGroup = array(); + + public $cascadingStrategy = CascadingStrategy::NONE; + + public $traversalStrategy = TraversalStrategy::IMPLICIT; + + /** + * Returns the names of the properties that should be serialized. + * + * @return array + */ + public function __sleep() + { + return array( + 'constraints', + 'constraintsByGroup', + 'cascadingStrategy', + 'traversalStrategy', + ); + } + + /** + * Clones this object. + */ + public function __clone() + { + $constraints = $this->constraints; + + $this->constraints = array(); + $this->constraintsByGroup = array(); + + foreach ($constraints as $constraint) { + $this->addConstraint(clone $constraint); + } + } + + /** + * Adds a constraint to this element. + * + * @param Constraint $constraint + * + * @return ElementMetadata + */ + public function addConstraint(Constraint $constraint) + { + if ($constraint instanceof Valid) { + $this->cascadingStrategy = CascadingStrategy::CASCADE; + + // Continue. Valid extends Traverse, so the return statement in the + // next block is going be executed. + } + + if ($constraint instanceof Traverse) { + if (true === $constraint->traverse) { + // If traverse is true, traversal should be explicitly enabled + $this->traversalStrategy = TraversalStrategy::TRAVERSE; + + if ($constraint->deep) { + $this->traversalStrategy |= TraversalStrategy::RECURSIVE; + } + } elseif (false === $constraint->traverse) { + // If traverse is false, traversal should be explicitly disabled + $this->traversalStrategy = TraversalStrategy::NONE; + } else { + // Else, traverse depending on the contextual information that + // is available during validation + $this->traversalStrategy = TraversalStrategy::IMPLICIT; + } + + // The constraint is not added + return $this; + } + + $this->constraints[] = $constraint; + + foreach ($constraint->groups as $group) { + $this->constraintsByGroup[$group][] = $constraint; + } + + return $this; + } + + public function addConstraints(array $constraints) + { + foreach ($constraints as $constraint) { + $this->addConstraint($constraint); + } + } + + /** + * Returns all constraints of this element. + * + * @return Constraint[] An array of Constraint instances + */ + public function getConstraints() + { + return $this->constraints; + } + + /** + * Returns whether this element has any constraints. + * + * @return Boolean + */ + public function hasConstraints() + { + return count($this->constraints) > 0; + } + + /** + * Returns the constraints of the given group and global ones (* group). + * + * @param string $group The group name + * + * @return array An array with all Constraint instances belonging to the group + */ + public function findConstraints($group) + { + return isset($this->constraintsByGroup[$group]) + ? $this->constraintsByGroup[$group] + : array(); + } + + public function getCascadingStrategy() + { + return $this->cascadingStrategy; + } + + public function getTraversalStrategy() + { + return $this->traversalStrategy; + } +} diff --git a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php index 876af1ae80..80a2687458 100644 --- a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php @@ -14,7 +14,6 @@ namespace Symfony\Component\Validator\Mapping; use Symfony\Component\Validator\ValidationVisitorInterface; use Symfony\Component\Validator\PropertyMetadataInterface as LegacyPropertyMetadataInterface; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface, LegacyPropertyMetadataInterface @@ -22,8 +21,6 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat public $class; public $name; public $property; - public $cascadingStrategy = CascadingStrategy::NONE; - public $traversalStrategy = TraversalStrategy::IMPLICIT; private $reflMember = array(); /** @@ -61,19 +58,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat )); } - if ($constraint instanceof Valid) { - $this->cascadingStrategy = CascadingStrategy::CASCADE; - - if ($constraint->traverse) { - $this->traversalStrategy = TraversalStrategy::TRAVERSE; - } - - if ($constraint->deep) { - $this->traversalStrategy |= TraversalStrategy::RECURSIVE; - } - } else { - parent::addConstraint($constraint); - } + parent::addConstraint($constraint); return $this; } @@ -89,8 +74,6 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat 'class', 'name', 'property', - 'cascadingStrategy', - 'traversalStrategy', )); } @@ -160,16 +143,6 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat return $this->getReflectionMember($objectOrClassName)->isPrivate(); } - public function getCascadingStrategy() - { - return $this->cascadingStrategy; - } - - public function getTraversalStrategy() - { - return $this->traversalStrategy; - } - /** * Returns whether objects stored in this member should be validated * diff --git a/src/Symfony/Component/Validator/Node/ValueNode.php b/src/Symfony/Component/Validator/Node/GenericNode.php similarity index 92% rename from src/Symfony/Component/Validator/Node/ValueNode.php rename to src/Symfony/Component/Validator/Node/GenericNode.php index e0f77e41f1..aab73db64e 100644 --- a/src/Symfony/Component/Validator/Node/ValueNode.php +++ b/src/Symfony/Component/Validator/Node/GenericNode.php @@ -15,6 +15,6 @@ namespace Symfony\Component\Validator\Node; * @since %%NextVersion%% * @author Bernhard Schussek */ -class ValueNode extends Node +class GenericNode extends Node { } diff --git a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php b/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php index 413e3fbc7d..54e3374f66 100644 --- a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php +++ b/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php @@ -12,10 +12,10 @@ namespace Symfony\Component\Validator\Tests\Context; use Symfony\Component\Validator\Context\ExecutionContext; -use Symfony\Component\Validator\Mapping\AdHocMetadata; +use Symfony\Component\Validator\Mapping\GenericMetadata; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Node\ClassNode; -use Symfony\Component\Validator\Node\ValueNode; +use Symfony\Component\Validator\Node\GenericNode; /** * @since 2.5 @@ -65,7 +65,7 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase public function testPushAndPop() { $metadata = $this->getMock('Symfony\Component\Validator\Mapping\MetadataInterface'); - $node = new ValueNode('value', $metadata, '', array(), array()); + $node = new GenericNode('value', $metadata, '', array(), array()); $this->context->pushNode($node); @@ -80,9 +80,9 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase public function testPushTwiceAndPop() { $metadata1 = $this->getMock('Symfony\Component\Validator\Mapping\MetadataInterface'); - $node1 = new ValueNode('value', $metadata1, '', array(), array()); + $node1 = new GenericNode('value', $metadata1, '', array(), array()); $metadata2 = $this->getMock('Symfony\Component\Validator\Mapping\MetadataInterface'); - $node2 = new ValueNode('other value', $metadata2, '', array(), array()); + $node2 = new GenericNode('other value', $metadata2, '', array(), array()); $this->context->pushNode($node1); $this->context->pushNode($node2); diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php index c314881320..d8a1c43500 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php @@ -42,22 +42,22 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase /** * @var ValidatorInterface */ - private $validator; + protected $validator; /** * @var FakeMetadataFactory */ - public $metadataFactory; + protected $metadataFactory; /** * @var ClassMetadata */ - public $metadata; + protected $metadata; /** * @var ClassMetadata */ - public $referenceMetadata; + protected $referenceMetadata; protected function setUp() { @@ -199,6 +199,45 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase $this->assertNull($violations[0]->getCode()); } + public function testArray() + { + $test = $this; + $entity = new Entity(); + $array = array('key' => $entity); + + $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $array) { + $test->assertSame($test::ENTITY_CLASS, $context->getClassName()); + $test->assertNull($context->getPropertyName()); + $test->assertSame('[key]', $context->getPropertyPath()); + $test->assertSame('Group', $context->getGroup()); + $test->assertSame($test->metadata, $context->getMetadata()); + $test->assertSame($test->metadataFactory, $context->getMetadataFactory()); + $test->assertSame($array, $context->getRoot()); + $test->assertSame($entity, $context->getValue()); + $test->assertSame($entity, $value); + + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + + $this->metadata->addConstraint(new Callback(array( + 'callback' => $callback, + 'groups' => 'Group', + ))); + + $violations = $this->validator->validate($array, 'Group'); + + /** @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters()); + $this->assertSame('[key]', $violations[0]->getPropertyPath()); + $this->assertSame($array, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getMessagePluralization()); + $this->assertNull($violations[0]->getCode()); + } + public function testReferenceClassConstraint() { $test = $this; @@ -815,14 +854,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase $this->assertNull($violations[0]->getCode()); } - /** - * @expectedException \Symfony\Component\Validator\Exception\ValidatorException - */ - public function testValidateValueRejectsValid() - { - $this->validator->validateValue(new Entity(), new Valid()); - } - public function testAddCustomizedViolation() { $entity = new Entity(); diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorTest.php index 327194d751..eafbc4d128 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorTest.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Validator\Tests\Validator; +use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Validator; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; @@ -37,4 +39,12 @@ class LegacyValidatorTest extends AbstractValidatorTest { $this->markTestSkipped('Currently not supported'); } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ValidatorException + */ + public function testValidateValueRejectsValid() + { + $this->validator->validateValue(new Entity(), new Valid()); + } } diff --git a/src/Symfony/Component/Validator/Tests/Validator/ValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/ValidatorTest.php index f990f10e04..57a08fbc94 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/ValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/ValidatorTest.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Validator\Tests\Validator; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextManager; @@ -18,6 +22,7 @@ use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver; use Symfony\Component\Validator\NodeVisitor\NodeValidator; use Symfony\Component\Validator\NodeTraverser\NodeTraverser; +use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Validator\LegacyValidator; class ValidatorTest extends AbstractValidatorTest @@ -44,4 +49,43 @@ class ValidatorTest extends AbstractValidatorTest return $validator; } + + public function testValidateValueAcceptsValid() + { + $test = $this; + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) { + $test->assertSame($test::ENTITY_CLASS, $context->getClassName()); + $test->assertNull($context->getPropertyName()); + $test->assertSame('', $context->getPropertyPath()); + $test->assertSame('Group', $context->getGroup()); + $test->assertSame($test->metadata, $context->getMetadata()); + $test->assertSame($test->metadataFactory, $context->getMetadataFactory()); + $test->assertSame($entity, $context->getRoot()); + $test->assertSame($entity, $context->getValue()); + $test->assertSame($entity, $value); + + $context->addViolation('Message %param%', array('%param%' => 'value')); + }; + + $this->metadata->addConstraint(new Callback(array( + 'callback' => $callback, + 'groups' => 'Group', + ))); + + // This is the same as when calling validateObject() + $violations = $this->validator->validateValue($entity, new Valid(), 'Group'); + + /** @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getMessagePluralization()); + $this->assertNull($violations[0]->getCode()); + } } diff --git a/src/Symfony/Component/Validator/Validator/AbstractValidator.php b/src/Symfony/Component/Validator/Validator/AbstractValidator.php index c57473e751..959e98779f 100644 --- a/src/Symfony/Component/Validator/Validator/AbstractValidator.php +++ b/src/Symfony/Component/Validator/Validator/AbstractValidator.php @@ -12,15 +12,16 @@ namespace Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\ValidatorException; use Symfony\Component\Validator\Mapping\ClassMetadataInterface; -use Symfony\Component\Validator\Mapping\AdHocMetadata; +use Symfony\Component\Validator\Mapping\GenericMetadata; use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\Node\ClassNode; use Symfony\Component\Validator\Node\PropertyNode; -use Symfony\Component\Validator\Node\ValueNode; +use Symfony\Component\Validator\Node\GenericNode; use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; use Symfony\Component\Validator\Util\PropertyPath; @@ -97,6 +98,24 @@ abstract class AbstractValidator implements ValidatorInterface ))); } + protected function traverseCollection($collection, $groups = null, $deep = false) + { + $metadata = new GenericMetadata(); + $metadata->addConstraint(new Traverse(array( + 'traverse' => true, + 'deep' => $deep, + ))); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + + $this->nodeTraverser->traverse(array(new GenericNode( + $collection, + $metadata, + $this->defaultPropertyPath, + $groups, + $groups + ))); + } + protected function traverseProperty($object, $propertyName, $groups = null) { $classMetadata = $this->metadataFactory->getMetadataFor($object); @@ -165,10 +184,11 @@ abstract class AbstractValidator implements ValidatorInterface $constraints = array($constraints); } - $metadata = new AdHocMetadata($constraints); + $metadata = new GenericMetadata(); + $metadata->addConstraints($constraints); $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; - $this->nodeTraverser->traverse(array(new ValueNode( + $this->nodeTraverser->traverse(array(new GenericNode( $value, $metadata, $this->defaultPropertyPath, diff --git a/src/Symfony/Component/Validator/Validator/LegacyValidator.php b/src/Symfony/Component/Validator/Validator/LegacyValidator.php index f8a2cc74c1..b2fb4e5d35 100644 --- a/src/Symfony/Component/Validator/Validator/LegacyValidator.php +++ b/src/Symfony/Component/Validator/Validator/LegacyValidator.php @@ -21,7 +21,14 @@ class LegacyValidator extends Validator implements LegacyValidatorInterface { public function validate($value, $groups = null, $traverse = false, $deep = false) { - // TODO what about $traverse and $deep? + if (is_array($value)) { + $this->contextManager->startContext($value); + + $this->traverseCollection($value, $groups, $deep); + + return $this->contextManager->stopContext()->getViolations(); + } + return $this->validateObject($value, $groups); } diff --git a/src/Symfony/Component/Validator/Validator/Validator.php b/src/Symfony/Component/Validator/Validator/Validator.php index 0c0c38880b..fe55818e70 100644 --- a/src/Symfony/Component/Validator/Validator/Validator.php +++ b/src/Symfony/Component/Validator/Validator/Validator.php @@ -24,7 +24,7 @@ class Validator extends AbstractValidator /** * @var ExecutionContextManagerInterface */ - private $contextManager; + protected $contextManager; public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextManagerInterface $contextManager) {