From 93eb8a00cb0198010a4d9a39f9e1285dd517cbc6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 27 Apr 2024 12:02:12 +0200 Subject: [PATCH] handle edge cases when constructing constraints with named arguments --- .../Mapping/Loader/AbstractLoader.php | 12 +++++++ .../Mapping/Loader/XmlFileLoader.php | 8 ++++- .../Mapping/Loader/YamlFileLoader.php | 6 ++++ .../Fixtures/ConstraintWithNamedArguments.php | 33 +++++++++++++++++++ ...nstraintWithoutValueWithNamedArguments.php | 29 ++++++++++++++++ .../Mapping/Loader/XmlFileLoaderTest.php | 5 +++ .../Mapping/Loader/YamlFileLoaderTest.php | 5 +++ .../Mapping/Loader/constraint-mapping.xml | 16 +++++++++ .../Mapping/Loader/constraint-mapping.yml | 6 ++++ 9 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithNamedArguments.php create mode 100644 src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithoutValueWithNamedArguments.php diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php index 3f0c0aa46fa97..146b0dc9edf54 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php @@ -87,6 +87,18 @@ protected function newConstraint(string $name, mixed $options = null): Constrain } if ($this->namedArgumentsCache[$className] ??= (bool) (new \ReflectionMethod($className, '__construct'))->getAttributes(HasNamedArguments::class)) { + if (null === $options) { + return new $className(); + } + + if (!\is_array($options)) { + return new $className($options); + } + + if (1 === \count($options) && isset($options['value'])) { + return new $className($options['value']); + } + return new $className(...$options); } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php index 94d3f071e5a77..1c5f5287308df 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php @@ -80,7 +80,9 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array foreach ($nodes as $node) { if (\count($node) > 0) { if (\count($node->value) > 0) { - $options = $this->parseValues($node->value); + $options = [ + 'value' => $this->parseValues($node->value), + ]; } elseif (\count($node->constraint) > 0) { $options = $this->parseConstraints($node->constraint); } elseif (\count($node->option) > 0) { @@ -94,6 +96,10 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array $options = null; } + if (isset($options['groups']) && !\is_array($options['groups'])) { + $options['groups'] = (array) $options['groups']; + } + $constraints[] = $this->newConstraint((string) $node['name'], $options); } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php index e610b45427313..d17f96ef44741 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php @@ -91,6 +91,12 @@ protected function parseNodes(array $nodes): array $options = $this->parseNodes($options); } + if (null !== $options && (!\is_array($options) || array_is_list($options))) { + $options = [ + 'value' => $options, + ]; + } + $values[] = $this->newConstraint(key($childNodes), $options); } else { if (\is_array($childNodes)) { diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithNamedArguments.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithNamedArguments.php new file mode 100644 index 0000000000000..70579011c3c94 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithNamedArguments.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures; + +use Symfony\Component\Validator\Attribute\HasNamedArguments; +use Symfony\Component\Validator\Constraint; + +class ConstraintWithNamedArguments extends Constraint +{ + public $choices; + + #[HasNamedArguments] + public function __construct(array|string|null $choices = [], ?array $groups = null) + { + parent::__construct([], $groups); + + $this->choices = $choices; + } + + public function getTargets(): string + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithoutValueWithNamedArguments.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithoutValueWithNamedArguments.php new file mode 100644 index 0000000000000..af950fc139ad6 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/Fixtures/ConstraintWithoutValueWithNamedArguments.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures; + +use Symfony\Component\Validator\Attribute\HasNamedArguments; +use Symfony\Component\Validator\Constraint; + +class ConstraintWithoutValueWithNamedArguments extends Constraint +{ + #[HasNamedArguments] + public function __construct(?array $groups = null) + { + parent::__construct([], $groups); + } + + public function getTargets(): string + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php index 5ba519ab195c5..2385dc888b276 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -32,6 +32,8 @@ use Symfony\Component\Validator\Tests\Fixtures\Entity_81; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\GroupSequenceProviderEntity; +use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments; +use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments; class XmlFileLoaderTest extends TestCase { @@ -66,6 +68,9 @@ public function testLoadClassMetadata() $expected->addConstraint(new Callback('validateMeStatic')); $expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'])); $expected->addConstraint(new Traverse(false)); + $expected->addConstraint(new ConstraintWithNamedArguments('foo')); + $expected->addConstraint(new ConstraintWithNamedArguments(['foo', 'bar'])); + $expected->addConstraint(new ConstraintWithoutValueWithNamedArguments(['foo'])); $expected->addPropertyConstraint('firstName', new NotNull()); $expected->addPropertyConstraint('firstName', new Range(['min' => 3])); $expected->addPropertyConstraint('firstName', new Choice(['A', 'B'])); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php index a5c983939bcb2..e34e5466ed667 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -29,6 +29,8 @@ use Symfony\Component\Validator\Tests\Fixtures\Entity_81; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\GroupSequenceProviderEntity; +use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments; +use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments; class YamlFileLoaderTest extends TestCase { @@ -109,6 +111,9 @@ public function testLoadClassMetadata() $expected->addConstraint(new Callback('validateMe')); $expected->addConstraint(new Callback('validateMeStatic')); $expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'])); + $expected->addConstraint(new ConstraintWithoutValueWithNamedArguments()); + $expected->addConstraint(new ConstraintWithNamedArguments('foo')); + $expected->addConstraint(new ConstraintWithNamedArguments(['foo', 'bar'])); $expected->addPropertyConstraint('firstName', new NotNull()); $expected->addPropertyConstraint('firstName', new Range(['min' => 3])); $expected->addPropertyConstraint('firstName', new Choice(['A', 'B'])); 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 6183b074a65cb..8a7975f114137 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml @@ -36,6 +36,22 @@ false + + + foo + + + + + foo + bar + + + + + + + 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 0cf87cffe0a69..af091a89fad8b 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml @@ -15,6 +15,12 @@ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity: - Callback: validateMe - Callback: validateMeStatic - Callback: [Symfony\Component\Validator\Tests\Fixtures\CallbackClass, callback] + # Constraint with named arguments support without value + - Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments: ~ + # Constraint with named arguments support with scalar value + - Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments: foo + # Constraint with named arguments support with array value + - Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments: [foo, bar] properties: firstName: