From b795c0785cc9197440f3a977637403307740ed48 Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 2 Jun 2026 16:37:53 +0200 Subject: [PATCH] fix(symfony): filter nested constraint groups in Sequentially/Compound ValidatorPropertyMetadataFactory expanded Composite::getNestedConstraints() unfiltered. A nested NotBlank(groups: ['some group']) inside a Sequentially (in Default via Composite group union) leaked into the Default expansion and falsely marked the property required in the OpenAPI schema. Filter nested constraints by the active validation group. Closes #7777 --- ...ntiallyValidatedEntityWithNestedGroups.php | 25 +++++++++++++++++ .../ValidatorPropertyMetadataFactoryTest.php | 27 +++++++++++++++++++ .../ValidatorPropertyMetadataFactory.php | 6 ++++- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntityWithNestedGroups.php diff --git a/src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntityWithNestedGroups.php b/src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntityWithNestedGroups.php new file mode 100644 index 0000000000..740b2edb16 --- /dev/null +++ b/src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntityWithNestedGroups.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Symfony\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints as Assert; + +class DummySequentiallyValidatedEntityWithNestedGroups +{ + #[Assert\Sequentially([ + new Assert\NotBlank(groups: ['some group']), + new Assert\Range(min: '0.01'), + ])] + public ?string $dummy = null; +} diff --git a/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php index 3dbdc17d2a..9b743e9e01 100644 --- a/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php @@ -23,6 +23,7 @@ use ApiPlatform\Symfony\Tests\Fixtures\DummyNumericValidatedEntity; use ApiPlatform\Symfony\Tests\Fixtures\DummyRangeValidatedEntity; use ApiPlatform\Symfony\Tests\Fixtures\DummySequentiallyValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummySequentiallyValidatedEntityWithNestedGroups; use ApiPlatform\Symfony\Tests\Fixtures\DummyUniqueValidatedEntity; use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedChoiceEntity; use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedEntity; @@ -450,6 +451,32 @@ public function testCreateWithSequentiallyConstraint(): void $this->assertArrayHasKey('pattern', $schema); } + public function testSequentiallyConstraintDoesNotMarkRequiredFromNestedConstraintWithDifferentGroup(): void + { + $validatorClassMetadata = new ClassMetadata(DummySequentiallyValidatedEntityWithNestedGroups::class); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); + + $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); + $validatorMetadataFactory->getMetadataFor(DummySequentiallyValidatedEntityWithNestedGroups::class) + ->willReturn($validatorClassMetadata) + ->shouldBeCalled(); + + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create(DummySequentiallyValidatedEntityWithNestedGroups::class, 'dummy', [])->willReturn( + (new ApiProperty())->withNativeType(Type::string()) + )->shouldBeCalled(); + + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [] + ); + + $propertyMetadata = $validationPropertyMetadataFactory->create(DummySequentiallyValidatedEntityWithNestedGroups::class, 'dummy'); + + $this->assertFalse($propertyMetadata->isRequired()); + } + public function testCreateWithCompoundConstraint(): void { $validatorClassMetadata = new ClassMetadata(DummyCompoundValidatedEntity::class); diff --git a/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php b/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php index 8e23eebc31..1b4a3c1609 100644 --- a/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php +++ b/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php @@ -212,7 +212,11 @@ private function getPropertyConstraints( foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) { if ($propertyConstraint instanceof Sequentially || $propertyConstraint instanceof Compound) { - $constraints[] = $propertyConstraint->getNestedConstraints(); + foreach ($propertyConstraint->getNestedConstraints() as $nestedConstraint) { + if (\in_array($validationGroup, $nestedConstraint->groups, true)) { + $constraints[] = [$nestedConstraint]; + } + } } else { $constraints[] = [$propertyConstraint]; }