Skip to content

Commit 271b298

Browse files
committed
handle edge cases when constructing constraints with named arguments
1 parent 4761a08 commit 271b298

9 files changed

+119
-1
lines changed

Mapping/Loader/AbstractLoader.php

+12
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ protected function newConstraint(string $name, mixed $options = null): Constrain
8787
}
8888

8989
if ($this->namedArgumentsCache[$className] ??= (bool) (new \ReflectionMethod($className, '__construct'))->getAttributes(HasNamedArguments::class)) {
90+
if (null === $options) {
91+
return new $className();
92+
}
93+
94+
if (!\is_array($options)) {
95+
return new $className($options);
96+
}
97+
98+
if (1 === \count($options) && isset($options['value'])) {
99+
return new $className($options['value']);
100+
}
101+
90102
return new $className(...$options);
91103
}
92104

Mapping/Loader/XmlFileLoader.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
8080
foreach ($nodes as $node) {
8181
if (\count($node) > 0) {
8282
if (\count($node->value) > 0) {
83-
$options = $this->parseValues($node->value);
83+
$options = [
84+
'value' => $this->parseValues($node->value),
85+
];
8486
} elseif (\count($node->constraint) > 0) {
8587
$options = $this->parseConstraints($node->constraint);
8688
} elseif (\count($node->option) > 0) {
@@ -94,6 +96,10 @@ protected function parseConstraints(\SimpleXMLElement $nodes): array
9496
$options = null;
9597
}
9698

99+
if (isset($options['groups']) && !\is_array($options['groups'])) {
100+
$options['groups'] = (array) $options['groups'];
101+
}
102+
97103
$constraints[] = $this->newConstraint((string) $node['name'], $options);
98104
}
99105

Mapping/Loader/YamlFileLoader.php

+6
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ protected function parseNodes(array $nodes): array
9191
$options = $this->parseNodes($options);
9292
}
9393

94+
if (null !== $options && (!\is_array($options) || array_is_list($options))) {
95+
$options = [
96+
'value' => $options,
97+
];
98+
}
99+
94100
$values[] = $this->newConstraint(key($childNodes), $options);
95101
} else {
96102
if (\is_array($childNodes)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures;
13+
14+
use Symfony\Component\Validator\Attribute\HasNamedArguments;
15+
use Symfony\Component\Validator\Constraint;
16+
17+
class ConstraintWithNamedArguments extends Constraint
18+
{
19+
public $choices;
20+
21+
#[HasNamedArguments]
22+
public function __construct(array|string|null $choices = [], ?array $groups = null)
23+
{
24+
parent::__construct([], $groups);
25+
26+
$this->choices = $choices;
27+
}
28+
29+
public function getTargets(): string
30+
{
31+
return self::CLASS_CONSTRAINT;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures;
13+
14+
use Symfony\Component\Validator\Attribute\HasNamedArguments;
15+
use Symfony\Component\Validator\Constraint;
16+
17+
class ConstraintWithoutValueWithNamedArguments extends Constraint
18+
{
19+
#[HasNamedArguments]
20+
public function __construct(?array $groups = null)
21+
{
22+
parent::__construct([], $groups);
23+
}
24+
25+
public function getTargets(): string
26+
{
27+
return self::CLASS_CONSTRAINT;
28+
}
29+
}

Tests/Mapping/Loader/XmlFileLoaderTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
use Symfony\Component\Validator\Tests\Fixtures\Entity_81;
3333
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity;
3434
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\GroupSequenceProviderEntity;
35+
use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments;
36+
use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments;
3537

3638
class XmlFileLoaderTest extends TestCase
3739
{
@@ -66,6 +68,9 @@ public function testLoadClassMetadata()
6668
$expected->addConstraint(new Callback('validateMeStatic'));
6769
$expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback']));
6870
$expected->addConstraint(new Traverse(false));
71+
$expected->addConstraint(new ConstraintWithNamedArguments('foo'));
72+
$expected->addConstraint(new ConstraintWithNamedArguments(['foo', 'bar']));
73+
$expected->addConstraint(new ConstraintWithoutValueWithNamedArguments(['foo']));
6974
$expected->addPropertyConstraint('firstName', new NotNull());
7075
$expected->addPropertyConstraint('firstName', new Range(['min' => 3]));
7176
$expected->addPropertyConstraint('firstName', new Choice(['A', 'B']));

Tests/Mapping/Loader/YamlFileLoaderTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
use Symfony\Component\Validator\Tests\Fixtures\Entity_81;
3030
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity;
3131
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\GroupSequenceProviderEntity;
32+
use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments;
33+
use Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments;
3234

3335
class YamlFileLoaderTest extends TestCase
3436
{
@@ -109,6 +111,9 @@ public function testLoadClassMetadata()
109111
$expected->addConstraint(new Callback('validateMe'));
110112
$expected->addConstraint(new Callback('validateMeStatic'));
111113
$expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback']));
114+
$expected->addConstraint(new ConstraintWithoutValueWithNamedArguments());
115+
$expected->addConstraint(new ConstraintWithNamedArguments('foo'));
116+
$expected->addConstraint(new ConstraintWithNamedArguments(['foo', 'bar']));
112117
$expected->addPropertyConstraint('firstName', new NotNull());
113118
$expected->addPropertyConstraint('firstName', new Range(['min' => 3]));
114119
$expected->addPropertyConstraint('firstName', new Choice(['A', 'B']));

Tests/Mapping/Loader/constraint-mapping.xml

+16
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@
3636
false
3737
</constraint>
3838

39+
<!-- Constraint with named arguments support with scalar value -->
40+
<constraint name="Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments">
41+
foo
42+
</constraint>
43+
44+
<!-- Constraint with named arguments support with array value -->
45+
<constraint name="Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments">
46+
<value>foo</value>
47+
<value>bar</value>
48+
</constraint>
49+
50+
<!-- Constraint with named arguments support with exactly one group -->
51+
<constraint name="Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments">
52+
<option name="groups">foo</option>
53+
</constraint>
54+
3955
<!-- PROPERTY CONSTRAINTS -->
4056

4157
<property name="firstName">

Tests/Mapping/Loader/constraint-mapping.yml

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity:
1515
- Callback: validateMe
1616
- Callback: validateMeStatic
1717
- Callback: [Symfony\Component\Validator\Tests\Fixtures\CallbackClass, callback]
18+
# Constraint with named arguments support without value
19+
- Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithoutValueWithNamedArguments: ~
20+
# Constraint with named arguments support with scalar value
21+
- Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments: foo
22+
# Constraint with named arguments support with array value
23+
- Symfony\Component\Validator\Tests\Mapping\Loader\Fixtures\ConstraintWithNamedArguments: [foo, bar]
1824

1925
properties:
2026
firstName:

0 commit comments

Comments
 (0)