From ef2bad61bfadd3470ef69d617ceefdfbb7bf9021 Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:09 -0500 Subject: [PATCH 1/6] [Validator] Target aware constraint's interface and trait --- .../TargetAwareConstraintInterface.php | 31 ++++++++++++++++ .../TargetAwareConstraintTrait.php | 37 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/Symfony/Component/Validator/Constraints/TargetAwareConstraintInterface.php create mode 100644 src/Symfony/Component/Validator/Constraints/TargetAwareConstraintTrait.php diff --git a/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintInterface.php b/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintInterface.php new file mode 100644 index 0000000000000..ed07c1f275119 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintInterface.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; + +/** + * This interface is to be implemented by constraints that need to be + * aware of the exact target they have been declared on. + * + * This interface only makes sense for class constraints. Which could be + * attached to multiple classes because of class inheritance. + * + * @since 3.1 + * + * @author Mathieu Lemoine + */ +interface TargetAwareConstraintInterface +{ + /* + * Since constraints are implemented using public properties, + * the interface is intended as a tag and declare no method. + */ +} diff --git a/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintTrait.php b/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintTrait.php new file mode 100644 index 0000000000000..0de2444ce932e --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/TargetAwareConstraintTrait.php @@ -0,0 +1,37 @@ + + * + * 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; + +/** + * This trait is intended as a helper for implementing TargetAwareConstraintInterface. + * + * Since the interface interface only makes sense for class constraints, + * the default targets is set to class constraint. + * + * @since 3.1 + * + * @author Mathieu Lemoine + */ +trait TargetAwareConstraintTrait +{ + public $target; + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return Constraint::CLASS_CONSTRAINT; + } +} From 9427089279be6226087476dc0b157ef0c2b484a3 Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:32 -0500 Subject: [PATCH 2/6] [Validator] Target aware constraint test fixture --- .../Validator/Tests/Fixtures/ConstraintD.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/ConstraintD.php diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintD.php b/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintD.php new file mode 100644 index 0000000000000..73f0d3d0f0e0d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintD.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\TargetAwareConstraintInterface; +use Symfony\Component\Validator\Constraints\TargetAwareConstraintTrait; + +/** @Annotation */ +class ConstraintD extends Constraint implements TargetAwareConstraintInterface +{ + use TargetAwareConstraintTrait; +} From 42dc2d2d1db389250dcd8cca297a5debff3ad4b0 Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:35 -0500 Subject: [PATCH 3/6] [Validator] Target aware constraint support for YamlFileLoader --- .../Mapping/Loader/YamlFileLoader.php | 5 ++ .../Mapping/Loader/YamlFileLoaderTest.php | 69 +++++++++++++++++++ .../Mapping/Loader/constraint-mapping.yml | 8 +++ 3 files changed, 82 insertions(+) diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php index cf6dd92dcad9b..cbba7b1c501d3 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Mapping\Loader; use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Constraints\TargetAwareConstraintInterface; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser as YamlParser; @@ -157,6 +158,10 @@ private function loadClassMetadataFromYaml(ClassMetadata $metadata, array $class if (isset($classDescription['constraints']) && is_array($classDescription['constraints'])) { foreach ($this->parseNodes($classDescription['constraints']) as $constraint) { + if ($constraint instanceof TargetAwareConstraintInterface) { + $constraint->target = $metadata->getClassName(); + } + $metadata->addConstraint($constraint); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php index 2ca64122aafae..89133631b14c2 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; +use Symfony\Component\Validator\Tests\Fixtures\ConstraintD; class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase { @@ -119,6 +120,74 @@ public function testLoadClassMetadata() $this->assertEquals($expected, $metadata); } + /** + * Test MetaData merge with parent annotation. + */ + public function testLoadParentClassMetadata() + { + $loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml'); + + // Load Parent MetaData + $parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $loader->loadClassMetadata($parent_metadata); + + $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); + $expected_parent->addPropertyConstraint('other', new NotNull()); + + $this->assertEquals($expected_parent, $parent_metadata); + } + /** + * Test MetaData merge with parent annotation. + */ + public function testLoadClassMetadataAndMerge() + { + $loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml'); + + // Load Parent MetaData + $parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $loader->loadClassMetadata($parent_metadata); + + $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); + + // Merge parent metaData. + $metadata->mergeConstraints($parent_metadata); + + $loader->loadClassMetadata($metadata); + + $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); + $expected_parent->addPropertyConstraint('other', new NotNull()); + + $expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); + $expected->mergeConstraints($expected_parent); + + $expected->setGroupSequence(array('Foo', 'Entity')); + $expected->addConstraint(new ConstraintA()); + $expected->addConstraint(new ConstraintB()); + $expected->addConstraint(new Callback('validateMe')); + $expected->addConstraint(new Callback('validateMeStatic')); + $expected->addConstraint(new Callback(array('Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'))); + $expected->addPropertyConstraint('firstName', new NotNull()); + $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); + $expected->addPropertyConstraint('firstName', new Choice(array('A', 'B'))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); + $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( + 'foo' => array(new NotNull(), new Range(array('min' => 3))), + 'bar' => array(new Range(array('min' => 5))), + )))); + $expected->addPropertyConstraint('firstName', new Choice(array( + 'message' => 'Must be one of %choices%', + 'choices' => array('A', 'B'), + ))); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); + + $this->assertEquals($expected, $metadata); + } + public function testLoadGroupSequenceProvider() { $loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml'); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml index c39168c96cac0..113038feee197 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml @@ -1,6 +1,14 @@ namespaces: custom: Symfony\Component\Validator\Tests\Fixtures\ +Symfony\Component\Validator\Tests\Fixtures\EntityParent: + constraints: + # Target aware constraint + - Symfony\Component\Validator\Tests\Fixtures\ConstraintD: ~ + properties: + other: + - NotNull: ~ + Symfony\Component\Validator\Tests\Fixtures\Entity: group_sequence: - Foo From 04d3bdccbdc1cd0f7176921cd855bc3a99e4153d Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:36 -0500 Subject: [PATCH 4/6] [Validator] Target aware constraint support for XmlFileLoader --- .../Mapping/Loader/XmlFileLoader.php | 5 ++ .../Mapping/Loader/XmlFileLoaderTest.php | 69 +++++++++++++++++++ .../Mapping/Loader/constraint-mapping.xml | 9 +++ 3 files changed, 83 insertions(+) diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php index 3458c7146a8f9..079ac5c00cab9 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Mapping\Loader; use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Validator\Constraints\TargetAwareConstraintInterface; use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -201,6 +202,10 @@ private function loadClassMetadataFromXml(ClassMetadata $metadata, $classDescrip } foreach ($this->parseConstraints($classDescription->constraint) as $constraint) { + if ($constraint instanceof TargetAwareConstraintInterface) { + $constraint->target = $metadata->getClassName(); + } + $metadata->addConstraint($constraint); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php index e6326b9a3154d..e31350692e272 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; +use Symfony\Component\Validator\Tests\Fixtures\ConstraintD; class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase { @@ -77,6 +78,74 @@ public function testLoadClassMetadata() $this->assertEquals($expected, $metadata); } + /** + * Test MetaData merge with parent annotation. + */ + public function testLoadParentClassMetadata() + { + $loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml'); + + // Load Parent MetaData + $parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $loader->loadClassMetadata($parent_metadata); + + $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); + $expected_parent->addPropertyConstraint('other', new NotNull()); + + $this->assertEquals($expected_parent, $parent_metadata); + } + /** + * Test MetaData merge with parent annotation. + */ + public function testLoadClassMetadataAndMerge() + { + $loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml'); + + // Load Parent MetaData + $parent_metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $loader->loadClassMetadata($parent_metadata); + + $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); + + // Merge parent metaData. + $metadata->mergeConstraints($parent_metadata); + + $loader->loadClassMetadata($metadata); + + $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); + $expected_parent->addPropertyConstraint('other', new NotNull()); + + $expected = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); + $expected->mergeConstraints($expected_parent); + + $expected->setGroupSequence(array('Foo', 'Entity')); + $expected->addConstraint(new ConstraintA()); + $expected->addConstraint(new ConstraintB()); + $expected->addConstraint(new Callback('validateMe')); + $expected->addConstraint(new Callback('validateMeStatic')); + $expected->addConstraint(new Callback(array('Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'))); + $expected->addPropertyConstraint('firstName', new NotNull()); + $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); + $expected->addPropertyConstraint('firstName', new Choice(array('A', 'B'))); + $expected->addPropertyConstraint('firstName', new All(array(new NotNull(), new Range(array('min' => 3))))); + $expected->addPropertyConstraint('firstName', new All(array('constraints' => array(new NotNull(), new Range(array('min' => 3)))))); + $expected->addPropertyConstraint('firstName', new Collection(array('fields' => array( + 'foo' => array(new NotNull(), new Range(array('min' => 3))), + 'bar' => array(new Range(array('min' => 5))), + )))); + $expected->addPropertyConstraint('firstName', new Choice(array( + 'message' => 'Must be one of %choices%', + 'choices' => array('A', 'B'), + ))); + $expected->addGetterConstraint('lastName', new NotNull()); + $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterConstraint('permissions', new IsTrue()); + + $this->assertEquals($expected, $metadata); + } + public function testLoadClassMetadataWithNonStrings() { $loader = new XmlFileLoader(__DIR__.'/constraint-mapping-non-strings.xml'); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml index 689184ecf2d81..a50e0b0af0cff 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml @@ -6,6 +6,15 @@ Symfony\Component\Validator\Tests\Fixtures\ + + + + + + + + + From c02329d1475cdfa0f8032efdfaccbc05407dd77e Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:38 -0500 Subject: [PATCH 5/6] [Validator] Target aware constraint support for AnnotationLoader --- .../Component/Validator/Mapping/Loader/AnnotationLoader.php | 5 +++++ .../Component/Validator/Tests/Fixtures/EntityParent.php | 3 +++ .../Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php index b95ef164a346b..42ff3ee860cc9 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php @@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\GroupSequenceProvider; +use Symfony\Component\Validator\Constraints\TargetAwareConstraintInterface; use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -51,6 +52,10 @@ public function loadClassMetadata(ClassMetadata $metadata) } elseif ($constraint instanceof GroupSequenceProvider) { $metadata->setGroupSequenceProvider(true); } elseif ($constraint instanceof Constraint) { + if ($constraint instanceof TargetAwareConstraintInterface) { + $constraint->target = $metadata->getClassName(); + } + $metadata->addConstraint($constraint); } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php index 422bb28d0b5ea..6e95dde06b00c 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php @@ -13,6 +13,9 @@ use Symfony\Component\Validator\Constraints\NotNull; +/** + * @Symfony\Component\Validator\Tests\Fixtures\ConstraintD + */ class EntityParent { protected $firstName; diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php index 5a2beff29a109..675c7402372ab 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; +use Symfony\Component\Validator\Tests\Fixtures\ConstraintD; class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase { @@ -89,6 +90,7 @@ public function testLoadParentClassMetadata() $loader->loadClassMetadata($parent_metadata); $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); $expected_parent->addPropertyConstraint('other', new NotNull()); $expected_parent->getReflectionClass(); @@ -113,6 +115,7 @@ public function testLoadClassMetadataAndMerge() $loader->loadClassMetadata($metadata); $expected_parent = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\EntityParent'); + $expected_parent->addConstraint(new ConstraintD(array('target' => 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'))); $expected_parent->addPropertyConstraint('other', new NotNull()); $expected_parent->getReflectionClass(); From 697833b89159710bc4def52729e8d69a5d003e3c Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Fri, 11 Dec 2015 19:23:41 -0500 Subject: [PATCH 6/6] Update CHANGELOG --- src/Symfony/Component/Validator/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 0ff667b770685..16e7fb491d7ec 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.1.0 +----- + + * added Target Aware Constraints and its support in loaders for class constraints + 2.8.0 -----