From 3eb056f831a7a479fa2dec7d3878b195113f24a7 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 13 Jun 2025 14:26:20 +0200 Subject: [PATCH] fix(metadata): call dynamic validation groups #7184 --- .../ValidatorPropertyMetadataFactory.php | 22 +++++--- .../ValidationGroupsExtractorTrait.php | 50 +++++++++++++++++++ src/Symfony/Validator/Validator.php | 26 ++-------- 3 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 src/Symfony/Validator/ValidationGroupsExtractorTrait.php diff --git a/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php b/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php index 4b27fa11f3a..5aba1bb66da 100644 --- a/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php +++ b/src/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php @@ -17,6 +17,8 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface; +use ApiPlatform\Symfony\Validator\ValidationGroupsExtractorTrait; +use Psr\Container\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Bic; use Symfony\Component\Validator\Constraints\CardScheme; @@ -47,6 +49,10 @@ */ final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryInterface { + use ValidationGroupsExtractorTrait { + getValidationGroups as extractValidationGroups; + } + /** * @var string[] A list of constraint classes making the entity required */ @@ -72,8 +78,13 @@ final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryI /** * @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata */ - public function __construct(private readonly ValidatorMetadataFactoryInterface $validatorMetadataFactory, private readonly PropertyMetadataFactoryInterface $decorated, private readonly iterable $restrictionsMetadata = []) - { + public function __construct( + private readonly ValidatorMetadataFactoryInterface $validatorMetadataFactory, + private readonly PropertyMetadataFactoryInterface $decorated, + private readonly iterable $restrictionsMetadata = [], + ?ContainerInterface $container = null, + ) { + $this->container = $container; } /** @@ -151,11 +162,8 @@ public function create(string $resourceClass, string $property, array $options = */ private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array { - if ( - isset($options['validation_groups']) - && !\is_callable($options['validation_groups']) - ) { - return $options['validation_groups']; + if (null !== ($groups = $this->extractValidationGroups($options['validation_groups'] ?? null))) { + return $groups; } if (!method_exists($classMetadata, 'getDefaultGroup')) { diff --git a/src/Symfony/Validator/ValidationGroupsExtractorTrait.php b/src/Symfony/Validator/ValidationGroupsExtractorTrait.php new file mode 100644 index 00000000000..b154a13ec7c --- /dev/null +++ b/src/Symfony/Validator/ValidationGroupsExtractorTrait.php @@ -0,0 +1,50 @@ + + * + * 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\Validator; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Validator\Constraints\GroupSequence; + +trait ValidationGroupsExtractorTrait +{ + /** + * A service locator for ValidationGroupsGenerator. + */ + private ?ContainerInterface $container = null; + + public function getValidationGroups(\Closure|array|string|null $validationGroups, ?object $data = null): string|array|GroupSequence|null + { + if (null === $validationGroups) { + return $validationGroups; + } + + if ( + $this->container + && \is_string($validationGroups) + && $this->container->has($validationGroups) + && ($service = $this->container->get($validationGroups)) + && \is_callable($service) + ) { + $validationGroups = $service($data); + } elseif (\is_callable($validationGroups)) { + $validationGroups = $validationGroups($data); + } + + if (!$validationGroups instanceof GroupSequence) { + $validationGroups = (array) $validationGroups; + } + + return $validationGroups; + } +} diff --git a/src/Symfony/Validator/Validator.php b/src/Symfony/Validator/Validator.php index a0645ab9968..0b21eebe3b5 100644 --- a/src/Symfony/Validator/Validator.php +++ b/src/Symfony/Validator/Validator.php @@ -16,7 +16,6 @@ use ApiPlatform\Validator\Exception\ValidationException; use ApiPlatform\Validator\ValidatorInterface; use Psr\Container\ContainerInterface; -use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Validator\ValidatorInterface as SymfonyValidatorInterface; /** @@ -26,8 +25,11 @@ */ final class Validator implements ValidatorInterface { - public function __construct(private readonly SymfonyValidatorInterface $validator, private readonly ?ContainerInterface $container = null) + use ValidationGroupsExtractorTrait; + + public function __construct(private readonly SymfonyValidatorInterface $validator, ?ContainerInterface $container = null) { + $this->container = $container; } /** @@ -35,25 +37,7 @@ public function __construct(private readonly SymfonyValidatorInterface $validato */ public function validate(object $data, array $context = []): void { - if (null !== $validationGroups = $context['groups'] ?? null) { - if ( - $this->container - && \is_string($validationGroups) - && $this->container->has($validationGroups) - && ($service = $this->container->get($validationGroups)) - && \is_callable($service) - ) { - $validationGroups = $service($data); - } elseif (\is_callable($validationGroups)) { - $validationGroups = $validationGroups($data); - } - - if (!$validationGroups instanceof GroupSequence) { - $validationGroups = (array) $validationGroups; - } - } - - $violations = $this->validator->validate($data, null, $validationGroups); + $violations = $this->validator->validate($data, null, $this->getValidationGroups($context['groups'] ?? null, $data)); if (0 !== \count($violations)) { throw new ValidationException($violations); }