From 651a7070562c5bfbb4f201259b077292b5dfaf28 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Sat, 4 Mar 2023 08:26:39 +0100 Subject: [PATCH] [Validator] Add the `excluded` option to the `Cascade` constraint --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Component/Validator/Constraints/Cascade.php | 10 +++++++++- .../Component/Validator/Mapping/ClassMetadata.php | 4 ++++ .../Validator/Tests/Mapping/ClassMetadataTest.php | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index d3cf3dd76dd4c..93407796bd055 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add a `NoSuspiciousCharacters` constraint to validate a string is not a spoofing attempt * Add the `countUnit` option to the `Length` constraint to allow counting the string length either by code points (like before, now the default setting `Length::COUNT_CODEPOINTS`), bytes (`Length::COUNT_BYTES`) or graphemes (`Length::COUNT_GRAPHEMES`) * Add the `filenameMaxLength` option to the `File` constraint + * Add the `exclude` option to the `Cascade` constraint 6.2 --- diff --git a/src/Symfony/Component/Validator/Constraints/Cascade.php b/src/Symfony/Component/Validator/Constraints/Cascade.php index f3bb3cf929fad..a1e03af411adc 100644 --- a/src/Symfony/Component/Validator/Constraints/Cascade.php +++ b/src/Symfony/Component/Validator/Constraints/Cascade.php @@ -23,8 +23,16 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class Cascade extends Constraint { - public function __construct(array $options = null) + public array $exclude = []; + + public function __construct(array|string|null $exclude = null, array $options = null) { + if (\is_array($exclude) && !array_is_list($exclude)) { + $options = array_merge($exclude, $options); + } else { + $this->exclude = array_flip((array) $exclude); + } + if (\is_array($options) && \array_key_exists('groups', $options)) { throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); } diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index 7909020b4d580..f5f084f2ed21c 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -194,6 +194,10 @@ public function addConstraint(Constraint $constraint): static $this->cascadingStrategy = CascadingStrategy::CASCADE; foreach ($this->getReflectionClass()->getProperties() as $property) { + if (isset($constraint->exclude[$property->getName()])) { + continue; + } + if ($property->hasType() && (('array' === $type = $property->getType()->getName()) || class_exists($type))) { $this->addPropertyConstraint($property->getName(), new Valid()); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php index ba3683cc19613..8e8d92160f10c 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php @@ -351,6 +351,20 @@ public function testCascadeConstraint() 'children', ], $metadata->getConstrainedProperties()); } + + public function testCascadeConstraintWithExcludedProperties() + { + $metadata = new ClassMetadata(CascadingEntity::class); + + $metadata->addConstraint(new Cascade(exclude: ['requiredChild', 'optionalChild'])); + + $this->assertSame(CascadingStrategy::CASCADE, $metadata->getCascadingStrategy()); + $this->assertCount(2, $metadata->properties); + $this->assertSame([ + 'staticChild', + 'children', + ], $metadata->getConstrainedProperties()); + } } class ClassCompositeConstraint extends Composite