diff --git a/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php b/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
index 01da4cf95ed..503ff2c8145 100644
--- a/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
+++ b/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
@@ -13,10 +13,11 @@
namespace ApiPlatform\GraphQl\Serializer\Exception;
+use ApiPlatform\Metadata\Exception\RuntimeException;
+use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface as LegacyConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
-use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -38,7 +39,7 @@ public function __construct(private readonly array $exceptionToStatus = [])
public function normalize(mixed $object, ?string $format = null, array $context = []): array
{
$validationException = $object->getPrevious();
- if (!$validationException instanceof ConstraintViolationListAwareExceptionInterface) {
+ if (!($validationException instanceof ConstraintViolationListAwareExceptionInterface || $validationException instanceof LegacyConstraintViolationListAwareExceptionInterface)) {
throw new RuntimeException(sprintf('Object is not a "%s".', ConstraintViolationListAwareExceptionInterface::class));
}
diff --git a/src/JsonApi/Serializer/ErrorNormalizer.php b/src/JsonApi/Serializer/ErrorNormalizer.php
index 55a2f12adbc..19764afa3bf 100644
--- a/src/JsonApi/Serializer/ErrorNormalizer.php
+++ b/src/JsonApi/Serializer/ErrorNormalizer.php
@@ -15,8 +15,8 @@
use ApiPlatform\Problem\Serializer\ErrorNormalizerTrait;
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
-use ApiPlatform\State\ApiResource\Error;
-use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
+use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface as LegacyConstraintViolationListAwareExceptionInterface;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;
@@ -48,7 +48,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
{
// TODO: in api platform 4 this will be the default, note that JSON:API is close to Problem so we should use the same normalizer
if ($context['rfc_7807_compliant_errors'] ?? false) {
- if ($object instanceof ConstraintViolationListAwareExceptionInterface) {
+ if ($object instanceof LegacyConstraintViolationListAwareExceptionInterface || $object instanceof ConstraintViolationListAwareExceptionInterface) {
// TODO: return ['errors' => $this->constraintViolationListNormalizer(...)]
return $this->constraintViolationListNormalizer->normalize($object->getConstraintViolationList(), $format, $context);
}
diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index db186069cee..2e6e5a8907d 100644
--- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -816,6 +816,7 @@ private function getFormats(array $configFormats): array
private function registerValidatorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
{
if (interface_exists(ValidatorInterface::class)) {
+ $container->setParameter('api_platform.validator.legacy_validation_exception', $config['validator']['legacy_validation_exception'] ?? true);
$loader->load('metadata/validator.xml');
$loader->load('validator/validator.xml');
diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php
index e5bf51e52d1..0056a07e9c1 100644
--- a/src/Symfony/Bundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php
@@ -22,6 +22,8 @@
use ApiPlatform\Metadata\Put;
use ApiPlatform\ParameterValidator\Exception\ValidationExceptionInterface;
use ApiPlatform\Symfony\Controller\MainController;
+use ApiPlatform\Symfony\Validator\Exception\ValidationException as LegacyValidationException;
+use ApiPlatform\Validator\Exception\ValidationException;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
use Doctrine\ORM\EntityManagerInterface;
@@ -94,6 +96,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->children()
->variableNode('serialize_payload_fields')->defaultValue([])->info('Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly.')->end()
->booleanNode('query_parameter_validation')->defaultValue(true)->end()
+ ->booleanNode('legacy_validation_exception')->defaultValue(true)->info('Uses the legacy "%s" instead of "%s".', LegacyValidationException::class, ValidationException::class)->end()
->end()
->end()
->arrayNode('eager_loading')
diff --git a/src/Symfony/Bundle/Resources/config/validator/validator.xml b/src/Symfony/Bundle/Resources/config/validator/validator.xml
index c5c66891d1c..8946534fa5d 100644
--- a/src/Symfony/Bundle/Resources/config/validator/validator.xml
+++ b/src/Symfony/Bundle/Resources/config/validator/validator.xml
@@ -8,6 +8,7 @@
+ %api_platform.validator.legacy_validation_exception%
diff --git a/src/Symfony/EventListener/ErrorListener.php b/src/Symfony/EventListener/ErrorListener.php
index 9a2a53331eb..87b376c0c5b 100644
--- a/src/Symfony/EventListener/ErrorListener.php
+++ b/src/Symfony/EventListener/ErrorListener.php
@@ -26,7 +26,8 @@
use ApiPlatform\Metadata\Util\ContentNegotiationTrait;
use ApiPlatform\State\ApiResource\Error;
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
-use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
+use ApiPlatform\State\Util\RequestAttributesExtractor;
+use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface as LegacyConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use Negotiation\Negotiator;
use Psr\Log\LoggerInterface;
@@ -192,7 +193,7 @@ private function getStatusCode(?HttpOperation $apiOperation, Request $request, ?
return 400;
}
- if ($exception instanceof ConstraintViolationListAwareExceptionInterface) {
+ if ($exception instanceof ConstraintViolationListAwareExceptionInterface || $exception instanceof LegacyConstraintViolationListAwareExceptionInterface) {
return 422;
}
diff --git a/src/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php b/src/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php
index 0d3dc6dd7e8..cf137c1ea9b 100644
--- a/src/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php
+++ b/src/Symfony/Validator/Exception/ConstraintViolationListAwareExceptionInterface.php
@@ -18,6 +18,8 @@
/**
* An exception which has a constraint violation list.
+ *
+ * @deprecated use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface
*/
interface ConstraintViolationListAwareExceptionInterface extends ExceptionInterface
{
diff --git a/src/Symfony/Validator/Exception/ValidationException.php b/src/Symfony/Validator/Exception/ValidationException.php
index 41faa0d1ac3..6100c94b72d 100644
--- a/src/Symfony/Validator/Exception/ValidationException.php
+++ b/src/Symfony/Validator/Exception/ValidationException.php
@@ -18,7 +18,7 @@
use ApiPlatform\Metadata\ErrorResource;
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
-use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface as ApiPlatformConstraintViolationListAwareExceptionInterface;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ValidationException as BaseValidationException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface as SymfonyHttpExceptionInterface;
use Symfony\Component\WebLink\Link;
@@ -71,6 +71,6 @@
],
graphQlOperations: []
)]
-final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface, ApiPlatformConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface, SymfonyHttpExceptionInterface
+final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface, SymfonyHttpExceptionInterface
{
}
diff --git a/src/Symfony/Validator/Validator.php b/src/Symfony/Validator/Validator.php
index 6bbd5b4b6ff..9580e008bd2 100644
--- a/src/Symfony/Validator/Validator.php
+++ b/src/Symfony/Validator/Validator.php
@@ -13,7 +13,8 @@
namespace ApiPlatform\Symfony\Validator;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
+use ApiPlatform\Symfony\Validator\Exception\ValidationException as LegacyValidationException;
+use ApiPlatform\Validator\Exception\ValidationException;
use ApiPlatform\Validator\ValidatorInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\Validator\Constraints\GroupSequence;
@@ -28,7 +29,7 @@
*/
class Validator implements ValidatorInterface
{
- public function __construct(private readonly SymfonyValidatorInterface $validator, private readonly ?ContainerInterface $container = null)
+ public function __construct(private readonly SymfonyValidatorInterface $validator, private readonly ?ContainerInterface $container = null, private readonly ?bool $legacyValidationException = true)
{
}
@@ -57,6 +58,9 @@ public function validate(object $data, array $context = []): void
$violations = $this->validator->validate($data, null, $validationGroups);
if (0 !== \count($violations)) {
+ if (true === $this->legacyValidationException) {
+ throw new LegacyValidationException($violations);
+ }
throw new ValidationException($violations);
}
}
diff --git a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
index f33e912c31b..c7259d7b7de 100644
--- a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
+++ b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
@@ -109,6 +109,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm
'validator' => [
'serialize_payload_fields' => [],
'query_parameter_validation' => true,
+ 'legacy_validation_exception' => true,
],
'name_converter' => null,
'enable_swagger' => true,
diff --git a/tests/Symfony/Validator/ValidatorTest.php b/tests/Symfony/Validator/ValidatorTest.php
index 7d11b46ff6e..14ecce8cf0a 100644
--- a/tests/Symfony/Validator/ValidatorTest.php
+++ b/tests/Symfony/Validator/ValidatorTest.php
@@ -13,10 +13,11 @@
namespace ApiPlatform\Tests\Symfony\Validator;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
+use ApiPlatform\Symfony\Validator\Exception\ValidationException as LegacyValidationException;
use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface;
use ApiPlatform\Symfony\Validator\Validator;
use ApiPlatform\Tests\Fixtures\DummyEntity;
+use ApiPlatform\Validator\Exception\ValidationException;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Container\ContainerInterface;
@@ -43,7 +44,7 @@ public function testValid(): void
$symfonyValidatorProphecy->validate($data, null, null)->willReturn($constraintViolationListProphecy->reveal())->shouldBeCalled();
$symfonyValidator = $symfonyValidatorProphecy->reveal();
- $validator = new Validator($symfonyValidator);
+ $validator = new Validator($symfonyValidator, legacyValidationException: false);
$validator->validate(new DummyEntity());
}
@@ -58,7 +59,25 @@ public function testInvalid(): void
$symfonyValidatorProphecy->validate($data, null, null)->willReturn($constraintViolationList)->shouldBeCalled();
$symfonyValidator = $symfonyValidatorProphecy->reveal();
- $validator = new Validator($symfonyValidator);
+ $validator = new Validator($symfonyValidator, legacyValidationException: false);
+ $validator->validate(new DummyEntity());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testDeprecatedInvalid(): void
+ {
+ $this->expectException(LegacyValidationException::class);
+
+ $data = new DummyEntity();
+ $constraintViolationList = new ConstraintViolationList([new ConstraintViolation('test', null, [], null, 'test', null), new ConstraintViolation('test', null, [], null, 'test', null)]);
+
+ $symfonyValidatorProphecy = $this->prophesize(SymfonyValidatorInterface::class);
+ $symfonyValidatorProphecy->validate($data, null, null)->willReturn($constraintViolationList)->shouldBeCalled();
+ $symfonyValidator = $symfonyValidatorProphecy->reveal();
+
+ $validator = new Validator($symfonyValidator, legacyValidationException: true);
$validator->validate(new DummyEntity());
}
@@ -74,7 +93,7 @@ public function testGetGroupsFromCallable(): void
$symfonyValidatorProphecy->validate($data, null, $expectedValidationGroups)->willReturn($constraintViolationListProphecy->reveal())->shouldBeCalled();
$symfonyValidator = $symfonyValidatorProphecy->reveal();
- $validator = new Validator($symfonyValidator);
+ $validator = new Validator($symfonyValidator, legacyValidationException: false);
$validator->validate(new DummyEntity(), ['groups' => fn ($data): array => $data instanceof DummyEntity ? $expectedValidationGroups : []]);
}
@@ -97,7 +116,7 @@ public function __invoke(object $object): array
}
});
- $validator = new Validator($symfonyValidatorProphecy->reveal(), $containerProphecy->reveal());
+ $validator = new Validator($symfonyValidatorProphecy->reveal(), $containerProphecy->reveal(), legacyValidationException: false);
$validator->validate(new DummyEntity(), ['groups' => 'groups_builder']);
}
@@ -116,7 +135,7 @@ public function testValidatorWithScalarGroup(): void
$containerProphecy = $this->prophesize(ContainerInterface::class);
$containerProphecy->has('foo')->willReturn(false)->shouldBeCalled();
- $validator = new Validator($symfonyValidator, $containerProphecy->reveal());
+ $validator = new Validator($symfonyValidator, $containerProphecy->reveal(), legacyValidationException: false);
$validator->validate(new DummyEntity(), ['groups' => 'foo']);
}
}