diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index 0c7574535e4fb..a3860df80945a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -21,7 +21,6 @@ use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\PropertyAccess\Exception\InvalidTypeException; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; @@ -285,6 +284,69 @@ public function testValidationNotPerformedWhenPartialDenormalizationReturnsViola } } + public function testNestedPayloadErrorReportingWhenPartialDenormalizationReturnsViolation() + { + $content = '{ + "name": "john doe", + "address": { + "address": "2332 street", + "zipcode": "20220", + "city": "Paris", + "country": "75000", + "geolocalization": { + "lng": 32.423 + } + } + }'; + $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver($serializer, $validator); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: $content); + $kernel = $this->createMock(HttpKernelInterface::class); + + // Test using use_class_as_default_expected_type = false context + $argument = new ArgumentMetadata('invalid-nested-payload', Employee::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(serializationContext: ['use_class_as_default_expected_type' => false]), + ]); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $validationFailedException = $e->getPrevious(); + $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException); + $this->assertSame( + sprintf('This value should be of type %s.', 'unknown'), + $validationFailedException->getViolations()[0]->getMessage() + ); + } + + // Test using use_class_as_default_expected_type context + $argument = new ArgumentMetadata('invalid-nested-payload', Employee::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(serializationContext: ['use_class_as_default_expected_type' => true]), + ]); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $validationFailedException = $e->getPrevious(); + $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException); + $this->assertSame( + sprintf('This value should be of type %s.', Geolocalization::class), + $validationFailedException->getViolations()[0]->getMessage() + ); + } + } + public function testUnsupportedMedia() { $serializer = new Serializer(); @@ -731,3 +793,34 @@ public function getPassword(): string return $this->password; } } + +class Employee +{ + public function __construct( + public string $name, + #[Assert\Valid] + public ?Address $address = null, + ) { + } +} + +class Address +{ + public function __construct( + public string $address, + public string $zipcode, + public string $city, + public string $country, + public Geolocalization $geolocalization, + ) { + } +} + +class Geolocalization +{ + public function __construct( + public string $lat, + public string $lng, + ) { + } +} diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b329cf1542334..6e3b5a89adc8d 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +7.1 +--- + * Add `AbstractNormalizer::USE_CLASS_AS_DEFAULT_EXPECTED_TYPE` in order to use the FQCN as the default value for NotNormalizableValueException's expectedTypes instead of unknown + 7.0 --- diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 7411c6415798a..38a3187652f19 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -118,6 +118,11 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn */ public const REQUIRE_ALL_PROPERTIES = 'require_all_properties'; + /** + * Use class name as default expected type when throwing NotNormalizableValueException instead of unknown. + */ + public const USE_CLASS_AS_DEFAULT_EXPECTED_TYPE = 'use_class_as_default_expected_type'; + /** * @internal */ @@ -380,7 +385,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $exception = NotNormalizableValueException::createForUnexpectedDataType( sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), $data, - ['unknown'], + [isset($context[self::USE_CLASS_AS_DEFAULT_EXPECTED_TYPE]) && $context[self::USE_CLASS_AS_DEFAULT_EXPECTED_TYPE] ? $class : 'unknown'], $context['deserialization_path'] ?? null, true ); @@ -424,12 +429,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex unset($context['has_constructor']); if (!$reflectionClass->isInstantiable()) { - throw NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to create object because the class "%s" is not instantiable.', $class), - $data, - ['unknown'], - $context['deserialization_path'] ?? null - ); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); } return new $class(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy