From 08678a45e750f8e39d7f50d8b7c6a0229e2be3b6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 28 Jul 2025 08:32:50 +0200 Subject: [PATCH] deprecate passing choices as $options argument to Choice constraint --- UPGRADE-7.4.md | 11 ++++---- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Validator/Constraints/Choice.php | 1 + .../Constraints/AtLeastOneOfValidatorTest.php | 2 +- .../Tests/Constraints/ChoiceTest.php | 2 +- .../Tests/Constraints/ChoiceValidatorTest.php | 17 ++++++------ .../Mapping/Loader/XmlFileLoaderTest.php | 20 ++++++++++++-- .../Mapping/Loader/YamlFileLoaderTest.php | 20 ++++++++++++-- .../constraint-mapping-value-option.xml | 27 +++++++++++++++++++ .../constraint-mapping-value-option.yml | 10 +++++++ .../Mapping/Loader/constraint-mapping.xml | 11 -------- .../Mapping/Loader/constraint-mapping.yml | 5 ---- 12 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.xml create mode 100644 src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.yml diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md index 63bd8866cef4b..abb49bfe6e20e 100644 --- a/UPGRADE-7.4.md +++ b/UPGRADE-7.4.md @@ -58,10 +58,11 @@ Translation Validator --------- + * Deprecate passing a list of choices to the first argument of the `Choice` constraint. Use the `choices` option instead * Deprecate `getRequiredOptions()` and `getDefaultOption()` methods of the `All`, `AtLeastOneOf`, `CardScheme`, `Collection`, `CssColor`, `Expression`, `Regex`, `Sequentially`, `Type`, and `When` constraints * Deprecate evaluating options in the base `Constraint` class. Initialize properties in the constructor of the concrete constraint - class instead. + class instead *Before* @@ -97,7 +98,7 @@ Validator } ``` - * Deprecate the `getRequiredOptions()` method of the base `Constraint` class. Use mandatory constructor arguments instead. + * Deprecate the `getRequiredOptions()` method of the base `Constraint` class. Use mandatory constructor arguments instead *Before* @@ -137,10 +138,10 @@ Validator } } ``` - * Deprecate the `normalizeOptions()` and `getDefaultOption()` methods of the base `Constraint` class without replacements. - Overriding them in child constraint will not have any effects starting with Symfony 8.0. + * Deprecate the `normalizeOptions()` and `getDefaultOption()` methods of the base `Constraint` class without replacements; + overriding them in child constraint will not have any effects starting with Symfony 8.0 * Deprecate passing an array of options to the `Composite` constraint class. Initialize the properties referenced with `getNestedConstraints()` - in child classes before calling the constructor of `Composite`. + in child classes before calling the constructor of `Composite` *Before* diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index f635c68daea5f..c95fc1b84ade7 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.4 --- + * Deprecate passing a list of choices to the first argument of the `Choice` constraint. Use the `choices` option instead * Add the `min` and `max` parameter to the `Length` constraint violation * Deprecate `getRequiredOptions()` and `getDefaultOption()` methods of the `All`, `AtLeastOneOf`, `CardScheme`, `Collection`, `CssColor`, `Expression`, `Regex`, `Sequentially`, `Type`, and `When` constraints diff --git a/src/Symfony/Component/Validator/Constraints/Choice.php b/src/Symfony/Component/Validator/Constraints/Choice.php index 8bc380735d1c4..cf353907d8e2b 100644 --- a/src/Symfony/Component/Validator/Constraints/Choice.php +++ b/src/Symfony/Component/Validator/Constraints/Choice.php @@ -85,6 +85,7 @@ public function __construct( ?bool $match = null, ) { if (\is_array($options) && $options && array_is_list($options)) { + trigger_deprecation('symfony/validator', '7.4', 'Support for passing the choices as the first argument to %s is deprecated.', static::class); $choices ??= $options; $options = null; } elseif (\is_array($options) && [] !== $options) { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php index 22b53dd13cbe1..59b737edc8fbd 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AtLeastOneOfValidatorTest.php @@ -317,7 +317,7 @@ public function testValidateNestedAtLeaseOneOfConstraints() new Collection([ 'bar' => new AtLeastOneOf([ new Type('int'), - new Choice(['test1', 'test2']), + new Choice(choices: ['test1', 'test2']), ]), ]), new Collection([ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceTest.php index 2173c45f52055..ddfb31b113e87 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceTest.php @@ -66,7 +66,7 @@ class ChoiceDummy #[Choice(choices: ['foo', 'bar'], message: 'myMessage')] private $b; - #[Choice([1, 2], groups: ['my_group'], payload: 'some attached data')] + #[Choice(choices: [1, 2], groups: ['my_group'], payload: 'some attached data')] private $c; #[Choice(choices: ['one' => 1, 'two' => 2])] diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php index 39affe4421a03..acfbb84195ce9 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php @@ -74,20 +74,21 @@ public function testValidCallbackExpected() $this->validator->validate('foobar', new Choice(callback: 'abcd')); } - /** - * @dataProvider provideConstraintsWithChoicesArray - */ - public function testValidChoiceArray(Choice $constraint) + public function testValidChoiceArray() { - $this->validator->validate('bar', $constraint); + $this->validator->validate('bar', new Choice(choices: ['foo', 'bar'])); $this->assertNoViolation(); } - public static function provideConstraintsWithChoicesArray(): iterable + /** + * @group legacy + */ + public function testValidChoiceArrayFirstArgument() { - yield 'first argument' => [new Choice(['foo', 'bar'])]; - yield 'named arguments' => [new Choice(choices: ['foo', 'bar'])]; + $this->validator->validate('bar', new Choice(['foo', 'bar'])); + + $this->assertNoViolation(); } /** diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php index 08a4bb862cbf1..3b0872df274fe 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Regex; use Symfony\Component\Validator\Constraints\Traverse; +use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; @@ -76,8 +77,6 @@ public function testLoadClassMetadata() $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'])); $expected->addPropertyConstraint('firstName', new All(constraints: [new NotNull(), new Range(min: 3)])); $expected->addPropertyConstraint('firstName', new All(constraints: [new NotNull(), new Range(min: 3)])); $expected->addPropertyConstraint('firstName', new Collection(fields: [ @@ -95,6 +94,23 @@ public function testLoadClassMetadata() $this->assertEquals($expected, $metadata); } + /** + * @group legacy + */ + public function testLoadClassMetadataValueOption() + { + $loader = new XmlFileLoader(__DIR__.'/constraint-mapping-value-option.xml'); + $metadata = new ClassMetadata(Entity::class); + + $loader->loadClassMetadata($metadata); + + $expected = new ClassMetadata(Entity::class); + $expected->addPropertyConstraint('firstName', new Type(type: 'string')); + $expected->addPropertyConstraint('firstName', new Choice(choices: ['A', 'B'])); + + $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/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php index c3bbcb18e1683..9cf77fc38303a 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Range; +use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; use Symfony\Component\Validator\Tests\Dummy\DummyGroupProvider; @@ -120,8 +121,6 @@ public function testLoadClassMetadata() $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'])); $expected->addPropertyConstraint('firstName', new All(constraints: [new NotNull(), new Range(min: 3)])); $expected->addPropertyConstraint('firstName', new All(constraints: [new NotNull(), new Range(min: 3)])); $expected->addPropertyConstraint('firstName', new Collection(fields: [ @@ -139,6 +138,23 @@ public function testLoadClassMetadata() $this->assertEquals($expected, $metadata); } + /** + * @group legacy + */ + public function testLoadClassMetadataValueOption() + { + $loader = new YamlFileLoader(__DIR__.'/constraint-mapping-value-option.yml'); + $metadata = new ClassMetadata(Entity::class); + + $loader->loadClassMetadata($metadata); + + $expected = new ClassMetadata(Entity::class); + $expected->addPropertyConstraint('firstName', new Type(type: 'string')); + $expected->addPropertyConstraint('firstName', new Choice(choices: ['A', 'B'])); + + $this->assertEquals($expected, $metadata); + } + public function testLoadClassMetadataWithConstants() { $loader = new YamlFileLoader(__DIR__.'/mapping-with-constants.yml'); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.xml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.xml new file mode 100644 index 0000000000000..d0fea931d4415 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.xml @@ -0,0 +1,27 @@ + + + + + Symfony\Component\Validator\Tests\Fixtures\ + + + + + + + + + string + + + + A + B + + + + + + diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.yml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.yml new file mode 100644 index 0000000000000..149497ad1b7b9 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping-value-option.yml @@ -0,0 +1,10 @@ +namespaces: + custom: Symfony\Component\Validator\Tests\Fixtures\ + +Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity: + properties: + firstName: + # Constraint with single value + - Type: string + # Constraint with multiple values + - 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 8a7975f114137..3666d3a757e4a 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml @@ -59,17 +59,6 @@ - - - - - - - - A - B - - 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 af091a89fad8b..06b0bd44f2aef 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml @@ -26,11 +26,6 @@ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity: firstName: # Constraint without value - NotNull: ~ - # Constraint with single value - - Range: - min: 3 - # Constraint with multiple values - - Choice: [A, B] # Constraint with child constraints - All: - NotNull: ~