From 6c4455fef7643a99e3eedeb92d77fa4a764e8bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=B6rl?= Date: Fri, 27 Jan 2012 02:33:45 +0100 Subject: [PATCH] [Validator] Added GroupSequenceProvider --- .../Constraints/GroupSequenceProvider.php | 31 +++++++++ .../Component/Validator/GraphWalker.php | 7 +- .../GroupSequenceProviderInterface.php | 28 ++++++++ .../Validator/Mapping/ClassMetadata.php | 66 +++++++++++++++++++ .../Mapping/Loader/AnnotationLoader.php | 3 + .../Mapping/Loader/XmlFileLoader.php | 4 ++ .../Mapping/Loader/YamlFileLoader.php | 4 ++ .../constraint-mapping-1.0.xsd | 10 +++ .../Component/Validator/Fixtures/Entity.php | 1 + .../Fixtures/GroupSequenceProvider.php | 20 ++++++ .../Validator/Mapping/ClassMetadataTest.php | 18 ++++- .../Mapping/Loader/AnnotationLoaderTest.php | 2 + .../Mapping/Loader/XmlFileLoaderTest.php | 1 + .../Mapping/Loader/YamlFileLoaderTest.php | 1 + .../Mapping/Loader/constraint-mapping.xml | 3 + .../Mapping/Loader/constraint-mapping.yml | 1 + .../Component/Validator/ValidatorTest.php | 47 +++++++++++++ 17 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Validator/Constraints/GroupSequenceProvider.php create mode 100644 src/Symfony/Component/Validator/GroupSequenceProviderInterface.php create mode 100644 tests/Symfony/Tests/Component/Validator/Fixtures/GroupSequenceProvider.php diff --git a/src/Symfony/Component/Validator/Constraints/GroupSequenceProvider.php b/src/Symfony/Component/Validator/Constraints/GroupSequenceProvider.php new file mode 100644 index 0000000000..c2b7e10787 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/GroupSequenceProvider.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Annotation to define a group sequence provider + * + * @Annotation + */ +class GroupSequenceProvider +{ + /** + * The name of the provider class + * @var string + */ + public $class; + + public function __construct(array $options) + { + $this->class = $options['value']; + } +} diff --git a/src/Symfony/Component/Validator/GraphWalker.php b/src/Symfony/Component/Validator/GraphWalker.php index dbdcfdb70e..5008748abb 100644 --- a/src/Symfony/Component/Validator/GraphWalker.php +++ b/src/Symfony/Component/Validator/GraphWalker.php @@ -67,8 +67,13 @@ class GraphWalker $initializer->initialize($object); } - if ($group === Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) { + if ($group === Constraint::DEFAULT_GROUP && ($metadata->hasGroupSequence() || $metadata->hasGroupSequenceProvider())) { $groups = $metadata->getGroupSequence(); + + if ($groupSequenceProvider = $metadata->getGroupSequenceProvider()) { + $groups = $groupSequenceProvider->getValidationGroups($object); + } + foreach ($groups as $group) { $this->walkObjectForGroup($metadata, $object, $group, $propertyPath, Constraint::DEFAULT_GROUP); diff --git a/src/Symfony/Component/Validator/GroupSequenceProviderInterface.php b/src/Symfony/Component/Validator/GroupSequenceProviderInterface.php new file mode 100644 index 0000000000..5aceffc7e5 --- /dev/null +++ b/src/Symfony/Component/Validator/GroupSequenceProviderInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * Defines the interface for a group sequence provider. + */ +interface GroupSequenceProviderInterface +{ + /** + * Returns which validation groups should be used for a certain state + * of the object. + * + * @param mixed $object The object that is validated. + * + * @return array An array of validation groups + */ + function getValidationGroups($object); +} diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index 237d854cbb..3dac6a9c09 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Mapping; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\GroupDefinitionException; +use Symfony\Component\Validator\GroupSequenceProviderInterface; /** * Represents all the configured constraints on a given class. @@ -29,6 +30,8 @@ class ClassMetadata extends ElementMetadata public $properties = array(); public $getters = array(); public $groupSequence = array(); + public $groupSequenceProviderClass; + public $groupSequenceProvider; private $reflClass; /** @@ -57,6 +60,7 @@ class ClassMetadata extends ElementMetadata return array_merge(parent::__sleep(), array( 'getters', 'groupSequence', + 'groupSequenceProviderClass', 'members', 'name', 'properties', @@ -293,4 +297,66 @@ class ClassMetadata extends ElementMetadata return $this->reflClass; } + + /** + * Sets the class name of the group sequence provider. + * + * @param string $class Sequence provider class name + */ + public function setGroupSequenceProviderClass($class) + { + $this->groupSequenceProviderClass = $class; + $this->groupSequenceProvider = null; + } + + /** + * Returns the name of the group sequence provider class. + * + * @return string Class name + */ + public function getGroupSequenceProviderClass() + { + return $this->groupSequenceProviderClass; + } + + /** + * Returns whether a group sequence provider is set. + * + * @return boolean + */ + public function hasGroupSequenceProvider() + { + return $this->groupSequenceProviderClass || $this->groupSequenceProvider; + } + + /** + * Returns the group sequence provider if specified. + * + * @return GroupSequenceProviderInterface The provider or null + */ + public function getGroupSequenceProvider() + { + if (!$this->groupSequenceProvider && $this->groupSequenceProviderClass) { + $reflClass = new \ReflectionClass($this->groupSequenceProviderClass); + $interface = 'Symfony\Component\Validator\GroupSequenceProviderInterface'; + + if (!$reflClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('The class "%s" must implement interface "%s".', $this->groupSequenceProviderClass, $interface)); + } + + $this->groupSequenceProvider = $reflClass->newInstance(); + } + + return $this->groupSequenceProvider; + } + + /** + * Sets the group sequence provider. + * + * @param GroupSequenceProviderInterface $provider Group sequence provider + */ + public function setGroupSequenceProvider(GroupSequenceProviderInterface $provider) + { + $this->groupSequenceProvider = $provider; + } } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php index 2474dba79f..630aa2ff31 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php @@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader; use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\GroupSequenceProvider; use Symfony\Component\Validator\Constraint; class AnnotationLoader implements LoaderInterface @@ -38,6 +39,8 @@ class AnnotationLoader implements LoaderInterface foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) { if ($constraint instanceof GroupSequence) { $metadata->setGroupSequence($constraint->groups); + } elseif ($constraint instanceof GroupSequenceProvider) { + $metadata->setGroupSequenceProviderClass($constraint->class); } elseif ($constraint instanceof Constraint) { $metadata->addConstraint($constraint); } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php index ef130d4b42..ae48f226ff 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php @@ -43,6 +43,10 @@ class XmlFileLoader extends FileLoader if (isset($this->classes[$metadata->getClassName()])) { $xml = $this->classes[$metadata->getClassName()]; + foreach ($xml->{'group-sequence-provider'} as $provider) { + $metadata->setGroupSequenceProviderClass($provider['class']); + } + foreach ($this->parseConstraints($xml->constraint) as $constraint) { $metadata->addConstraint($constraint); } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php index c25e49cab3..62750f40a5 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php @@ -54,6 +54,10 @@ class YamlFileLoader extends FileLoader if (isset($this->classes[$metadata->getClassName()])) { $yaml = $this->classes[$metadata->getClassName()]; + if (isset($yaml['group_sequence_provider'])) { + $metadata->setGroupSequenceProviderClass($yaml['group_sequence_provider']); + } + if (isset($yaml['constraints'])) { foreach ($this->parseNodes($yaml['constraints']) as $constraint) { $metadata->addConstraint($constraint); diff --git a/src/Symfony/Component/Validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd b/src/Symfony/Component/Validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd index 61232da570..f16a736d5e 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd +++ b/src/Symfony/Component/Validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd @@ -52,6 +52,7 @@ ]]> + @@ -59,6 +60,15 @@ + + + + + + + groups = $groups; + } + + public function getValidationGroups($object) + { + return $this->groups; + } +} diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php b/tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php index 5ec7abff8b..87d2ef4144 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php +++ b/tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php @@ -18,11 +18,13 @@ use Symfony\Tests\Component\Validator\Fixtures\Entity; use Symfony\Tests\Component\Validator\Fixtures\ConstraintA; use Symfony\Tests\Component\Validator\Fixtures\ConstraintB; use Symfony\Tests\Component\Validator\Fixtures\PropertyConstraint; +use Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider; require_once __DIR__.'/../Fixtures/Entity.php'; require_once __DIR__.'/../Fixtures/ConstraintA.php'; require_once __DIR__.'/../Fixtures/ConstraintB.php'; require_once __DIR__.'/../Fixtures/PropertyConstraint.php'; +require_once __DIR__.'/../Fixtures/GroupSequenceProvider.php'; class ClassMetadataTest extends \PHPUnit_Framework_TestCase { @@ -189,5 +191,19 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase $this->metadata->setGroupSequence(array('Foo', $this->metadata->getDefaultGroup(), Constraint::DEFAULT_GROUP)); } -} + public function testGroupSequenceProvider() + { + $this->assertNull($this->metadata->getGroupSequenceProvider()); + + $this->metadata->setGroupSequenceProviderClass('stdClass'); + + try { + $this->metadata->getGroupSequenceProvider(); + $this->fail(); + } catch(\InvalidArgumentException $e) {} + + $this->metadata->setGroupSequenceProviderClass('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider'); + $this->assertTrue($this->metadata->getGroupSequenceProvider() instanceof GroupSequenceProvider); + } +} diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/AnnotationLoaderTest.php b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/AnnotationLoaderTest.php index 58f215c9fa..adaeee9423 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/AnnotationLoaderTest.php +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/AnnotationLoaderTest.php @@ -73,6 +73,7 @@ class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); + $expected->setGroupSequenceProviderClass('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider'); // load reflection class so that the comparison passes $expected->getReflectionClass(); @@ -137,6 +138,7 @@ class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); + $expected->setGroupSequenceProviderClass('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider'); // load reflection class so that the comparison passes $expected->getReflectionClass(); diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php index 8746422aac..035c0f786b 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php @@ -67,6 +67,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); + $expected->setGroupSequenceProviderClass('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider'); $this->assertEquals($expected, $metadata); } diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/YamlFileLoaderTest.php b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/YamlFileLoaderTest.php index b661efbf94..de6c687b7f 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/YamlFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/YamlFileLoaderTest.php @@ -85,6 +85,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); + $expected->setGroupSequenceProviderClass('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider'); $this->assertEquals($expected, $metadata); } diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.xml b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.xml index 03c2467951..cd669e51f4 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.xml +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.xml @@ -8,6 +8,9 @@ + + + diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.yml b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.yml index ea1ba6fec9..0d0083ae59 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.yml +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/constraint-mapping.yml @@ -2,6 +2,7 @@ namespaces: custom: Symfony\Tests\Component\Validator\Fixtures\ Symfony\Tests\Component\Validator\Fixtures\Entity: + group_sequence_provider: Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider constraints: # Custom constraint - Symfony\Tests\Component\Validator\Fixtures\ConstraintA: ~ diff --git a/tests/Symfony/Tests/Component/Validator/ValidatorTest.php b/tests/Symfony/Tests/Component/Validator/ValidatorTest.php index b07e596a10..722f31472e 100644 --- a/tests/Symfony/Tests/Component/Validator/ValidatorTest.php +++ b/tests/Symfony/Tests/Component/Validator/ValidatorTest.php @@ -19,6 +19,7 @@ require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php'; use Symfony\Tests\Component\Validator\Fixtures\Entity; use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory; use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint; +use Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider; use Symfony\Component\Validator\Validator; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; @@ -122,6 +123,52 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals($violations, $result); } + public function testValidate_groupSequenceProvider() + { + $groupSequenceProvider = new GroupSequenceProvider; + + $entity = new Entity(); + $metadata = new ClassMetadata(get_class($entity)); + $metadata->addPropertyConstraint('firstName', new FailingConstraint(array( + 'groups' => 'First', + ))); + $metadata->addPropertyConstraint('lastName', new FailingConstraint(array( + 'groups' => 'Second', + ))); + $metadata->setGroupSequenceProvider($groupSequenceProvider); + $this->factory->addClassMetadata($metadata); + + $violations = new ConstraintViolationList(); + $violations->add(new ConstraintViolation( + '', + array(), + $entity, + 'firstName', + '' + )); + + $groupSequenceProvider->setGroups(array('First')); + $result = $this->validator->validate($entity); + $this->assertEquals($violations, $result); + + $violations = new ConstraintViolationList(); + $violations->add(new ConstraintViolation( + '', + array(), + $entity, + 'lastName', + '' + )); + + $groupSequenceProvider->setGroups(array('Second')); + $result = $this->validator->validate($entity); + $this->assertEquals($violations, $result); + + $groupSequenceProvider->setGroups(array()); + $result = $this->validator->validate($entity); + $this->assertEquals(new ConstraintViolationList(), $result); + } + public function testValidateProperty() { $entity = new Entity();