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: