diff --git a/CHANGELOG.md b/CHANGELOG.md index 2392df682c1..eff66c8204f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Improve `NotNormalizableValueException` exception messages in `BackedEnumNormalizer` to contain more useful information + 8.0 --- diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index e87c51ca1c3..15d7f47d9c1 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -69,7 +69,7 @@ public function getTotalTime(): float $totalTime = 0; foreach ($this->data as $handled) { - $totalTime += array_sum(array_map(fn (array $el): float => $el['time'], $handled)); + $totalTime += array_sum(array_map(static fn (array $el): float => $el['time'], $handled)); } return $totalTime; diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 3f2a4ec729d..017a270be12 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -84,12 +84,12 @@ public function process(ContainerBuilder $container): void private function createNamedSerializerTags(ContainerBuilder $container, string $tagName, string $configName, array $namedSerializers): void { $serializerNames = array_keys($namedSerializers); - $withBuiltIn = array_filter($serializerNames, fn (string $name) => $namedSerializers[$name][$configName] ?? false); + $withBuiltIn = array_filter($serializerNames, static fn (string $name) => $namedSerializers[$name][$configName] ?? false); foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) { $definition = $container->getDefinition($serviceId); - if (array_any($tags, $closure = fn (array $tag) => (bool) $tag)) { + if (array_any($tags, $closure = static fn (array $tag) => (bool) $tag)) { $tags = array_filter($tags, $closure); } diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 887544ff7ff..1e700a2d19e 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -102,7 +102,7 @@ private function doLoadClassMetadata(\ReflectionClass $reflectionClass, ClassMet } $attributeMetadata = $attributesMetadata[$property->name]; - if ($property->getDeclaringClass()->name === $className) { + if ($property->class === $className) { if ($classContextAttribute) { $this->setAttributeContextsForGroups($classContextAttribute, $attributeMetadata); } @@ -135,7 +135,7 @@ private function doLoadClassMetadata(\ReflectionClass $reflectionClass, ClassMet } foreach ($reflectionClass->getMethods() as $method) { - if ($method->getDeclaringClass()->name !== $className) { + if ($method->class !== $className) { continue; } $name = $method->name; @@ -147,6 +147,7 @@ private function doLoadClassMetadata(\ReflectionClass $reflectionClass, ClassMet $attributeName = $this->getAttributeNameFromAccessor($reflectionClass, $method, true); $accessorOrMutator = null !== $attributeName; $hasProperty = $this->hasPropertyForAccessor($method->getDeclaringClass(), $name); + $attributeMetadata = null; if ($hasProperty || $accessorOrMutator) { if (null === $attributeName || 's' !== $name[0] && $hasProperty && $this->hasAttributeNameCollision($reflectionClass, $attributeName, $name)) { @@ -163,7 +164,7 @@ private function doLoadClassMetadata(\ReflectionClass $reflectionClass, ClassMet foreach ($this->loadAttributes($method) as $attribute) { if ($attribute instanceof Groups) { - if (!$accessorOrMutator && !$hasProperty) { + if (!$attributeMetadata) { throw new MappingException(\sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has", "can" or "set".', $className, $method->name)); } @@ -171,27 +172,29 @@ private function doLoadClassMetadata(\ReflectionClass $reflectionClass, ClassMet $attributeMetadata->addGroup($group); } } elseif ($attribute instanceof MaxDepth) { - if (!$accessorOrMutator && !$hasProperty) { + if (!$attributeMetadata) { throw new MappingException(\sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has", "can" or "set".', $className, $method->name)); } $attributeMetadata->setMaxDepth($attribute->maxDepth); } elseif ($attribute instanceof SerializedName) { - if (!$accessorOrMutator && !$hasProperty) { + if (!$attributeMetadata) { throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has", "can" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedName($attribute->serializedName); } elseif ($attribute instanceof SerializedPath) { - if (!$accessorOrMutator && !$hasProperty) { + if (!$attributeMetadata) { throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has", "can" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedPath($attribute->serializedPath); } elseif ($attribute instanceof Ignore) { - $attributeMetadata->setIgnore(true); + if ($attributeMetadata) { + $attributeMetadata->setIgnore(true); + } } elseif ($attribute instanceof Context) { - if (!$accessorOrMutator && !$hasProperty) { + if (!$attributeMetadata) { throw new MappingException(\sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has", "can" or "set".', $className, $method->name)); } @@ -217,8 +220,8 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } $on = match (true) { $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, - $reflector instanceof \ReflectionMethod => \sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), - $reflector instanceof \ReflectionProperty => \sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionMethod => \sprintf(' on "%s::%s()"', $reflector->class, $reflector->name), + $reflector instanceof \ReflectionProperty => \sprintf(' on "%s::$%s"', $reflector->class, $reflector->name), default => '', }; diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index b82f0748913..7a9ff0e4c58 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -63,7 +63,7 @@ public function denormalize(string $propertyName, ?string $class = null, ?string throw new UnexpectedPropertyException($propertyName); } - $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), $propertyName); + $camelCasedName = preg_replace_callback('/(^|_|\.)++(.)/', static fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), $propertyName); if ($this->lowerCamelCase) { $camelCasedName = lcfirst($camelCasedName); diff --git a/NameConverter/SnakeCaseToCamelCaseNameConverter.php b/NameConverter/SnakeCaseToCamelCaseNameConverter.php index ab8a0635172..13f81f42f49 100644 --- a/NameConverter/SnakeCaseToCamelCaseNameConverter.php +++ b/NameConverter/SnakeCaseToCamelCaseNameConverter.php @@ -45,11 +45,7 @@ public function normalize(string $propertyName, ?string $class = null, ?string $ return $propertyName; } - $camelCasedName = preg_replace_callback( - '/(^|_|\.)+(.)/', - fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), - $propertyName - ); + $camelCasedName = preg_replace_callback('/(^|_|\.)++(.)/', static fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), $propertyName); if ($this->lowerCamelCase) { $camelCasedName = lcfirst($camelCasedName); diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 4aa7e385239..d258d1b7dba 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -56,7 +56,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a /** @var list|BuiltinType> */ $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; - $typeIdentifiers = array_map(fn ($t) => $t->getTypeIdentifier()->value, $keyTypes); + $typeIdentifiers = array_map(static fn ($t) => $t->getTypeIdentifier()->value, $keyTypes); } foreach ($data as $key => $value) { diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index ff5a782c49a..cac103f7344 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -77,7 +77,21 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return null; } - throw NotNormalizableValueException::createForUnexpectedDataType('The data must belong to a backed enumeration of type '.$type, $data, ['int', 'string'], $context['deserialization_path'] ?? null, true, 0, $e); + $backingType = (new \ReflectionEnum($type))->getBackingType()->getName(); + + if ($e instanceof \TypeError || get_debug_type($data) !== $backingType) { + throw NotNormalizableValueException::createForUnexpectedDataType('The data must be of type '.$backingType, $data, [$backingType], $context['deserialization_path'] ?? null, true, 0, $e); + } + + $expectedValues = array_map(static function ($type) { + if (\is_string($type->value)) { + return "'{$type->value}'"; + } + + return $type->value; + }, $type::cases()); + + throw new NotNormalizableValueException('The data must be one of the following values: '.implode(', ', $expectedValues), 0, $e, $type, null, $context['deserialization_path'] ?? null, true); } } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 8f255e6cee6..12500e0456a 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -65,13 +65,13 @@ public function normalize(mixed $object, ?string $format = null, array $context $exception = $exception->getPrevious(); if ($exception instanceof PartialDenormalizationException) { - $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); + $trans = $this->translator ? $this->translator->trans(...) : static fn ($m, $p) => strtr($m, $p); $template = 'This value should be of type {{ type }}.'; $error = [ self::TYPE => 'https://symfony.com/errors/validation', self::TITLE => 'Validation Failed', 'violations' => array_map( - fn ($e) => [ + static fn ($e) => [ 'propertyPath' => $e->getPath(), 'title' => $trans($template, [ '{{ type }}' => implode('|', $e->getExpectedTypes() ?? ['?']), @@ -84,7 +84,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $exception->getErrors() ), ]; - $error['detail'] = implode("\n", array_map(fn ($e) => $e['propertyPath'].': '.$e['title'], $error['violations'])); + $error['detail'] = implode("\n", array_map(static fn ($e) => $e['propertyPath'].': '.$e['title'], $error['violations'])); } elseif (($exception instanceof ValidationFailedException || $exception instanceof MessageValidationFailedException) && $this->serializer instanceof NormalizerInterface && $this->serializer->supportsNormalization($exception->getViolations(), $format, $context) diff --git a/Tests/Attribute/ContextTest.php b/Tests/Attribute/ContextTest.php index c54a023a71a..18726f5e3df 100644 --- a/Tests/Attribute/ContextTest.php +++ b/Tests/Attribute/ContextTest.php @@ -75,7 +75,7 @@ public function testValidInputs(callable $factory, string $expectedDump) public static function provideValidInputs(): iterable { yield 'named arguments: with context option' => [ - fn () => new Context(context: ['foo' => 'bar']), + static fn () => new Context(context: ['foo' => 'bar']), << [ - fn () => new Context(normalizationContext: ['foo' => 'bar']), + static fn () => new Context(normalizationContext: ['foo' => 'bar']), << [ - fn () => new Context(denormalizationContext: ['foo' => 'bar']), + static fn () => new Context(denormalizationContext: ['foo' => 'bar']), << [ - fn () => new Context(context: ['foo' => 'bar'], groups: 'a'), + static fn () => new Context(context: ['foo' => 'bar'], groups: 'a'), << [ - fn () => new Context(context: ['foo' => 'bar'], groups: ['a', 'b']), + static fn () => new Context(context: ['foo' => 'bar'], groups: ['a', 'b']), << [$data]; yield 'array iterator' => [new \ArrayIterator($data)]; yield 'iterator aggregate' => [new \IteratorIterator(new \ArrayIterator($data))]; - yield 'generator' => [(fn (): \Generator => yield from $data)()]; + yield 'generator' => [(static fn (): \Generator => yield from $data)()]; } } diff --git a/Tests/Fixtures/IntegerBackedEnumDummy.php b/Tests/Fixtures/IntegerBackedEnumDummy.php index 087da0f880a..8ad0044651a 100644 --- a/Tests/Fixtures/IntegerBackedEnumDummy.php +++ b/Tests/Fixtures/IntegerBackedEnumDummy.php @@ -5,4 +5,5 @@ enum IntegerBackedEnumDummy: int { case SUCCESS = 200; + case NOT_FOUND = 404; } diff --git a/Tests/Fixtures/StringBackedEnumDummy.php b/Tests/Fixtures/StringBackedEnumDummy.php index 15ec71c9b4d..77058be1c1b 100644 --- a/Tests/Fixtures/StringBackedEnumDummy.php +++ b/Tests/Fixtures/StringBackedEnumDummy.php @@ -5,4 +5,5 @@ enum StringBackedEnumDummy: string { case GET = 'GET'; + case OPTIONS = 'OPTIONS'; } diff --git a/Tests/Normalizer/BackedEnumNormalizerTest.php b/Tests/Normalizer/BackedEnumNormalizerTest.php index 2f76f735ed2..4c11eea094a 100644 --- a/Tests/Normalizer/BackedEnumNormalizerTest.php +++ b/Tests/Normalizer/BackedEnumNormalizerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -59,38 +60,88 @@ public function testSupportsDenormalization() $this->assertFalse($this->normalizer->supportsDenormalization(null, \stdClass::class)); } - public function testDenormalize() + #[TestWith([StringBackedEnumDummy::GET, 'GET', StringBackedEnumDummy::class], 'string backed enum')] + #[TestWith([IntegerBackedEnumDummy::SUCCESS, 200, IntegerBackedEnumDummy::class], 'int backed enum')] + #[TestWith([IntegerBackedEnumDummy::SUCCESS, '200', IntegerBackedEnumDummy::class], 'int backed enum with string value')] + public function testDenormalize(mixed $expected, mixed $data, string $type) { - $this->assertSame(StringBackedEnumDummy::GET, $this->normalizer->denormalize('GET', StringBackedEnumDummy::class)); - $this->assertSame(IntegerBackedEnumDummy::SUCCESS, $this->normalizer->denormalize(200, IntegerBackedEnumDummy::class)); + $this->assertSame($expected, $this->normalizer->denormalize($data, $type)); } public function testDenormalizeNullValueThrowsException() { $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('The data is neither an integer nor a string, you should pass an integer or a string'); + $this->normalizer->denormalize(null, StringBackedEnumDummy::class); } public function testDenormalizeBooleanValueThrowsException() { $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('The data is neither an integer nor a string, you should pass an integer or a string'); + $this->normalizer->denormalize(true, StringBackedEnumDummy::class); } public function testDenormalizeObjectThrowsException() { $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('The data is neither an integer nor a string, you should pass an integer or a string'); + $this->normalizer->denormalize(new \stdClass(), StringBackedEnumDummy::class); } - public function testDenormalizeBadBackingValueThrowsException() + public function testDenormalizeInvalidBackedTypeThrowsException() + { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('The data must be of type string'); + + $this->normalizer->denormalize(8, StringBackedEnumDummy::class); + } + + public function testDenormalizeInvalidIntegerBackedValueThrowsException() { $this->expectException(NotNormalizableValueException::class); - $this->expectExceptionMessage('The data must belong to a backed enumeration of type '.StringBackedEnumDummy::class); + $this->expectExceptionMessage('The data must be one of the following values: 200, 404'); + + $this->normalizer->denormalize(300, IntegerBackedEnumDummy::class); + } + + public function testDenormalizeInvalidStringBackedValueThrowsException() + { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage("The data must be one of the following values: 'GET', 'OPTIONS'"); $this->normalizer->denormalize('POST', StringBackedEnumDummy::class); } + public function testDenormalizeInvalidBackedValueWithAllowInvalidAndCollectErrorsThrows() + { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage("The data must be one of the following values: 'GET', 'OPTIONS'"); + + $context = [ + BackedEnumNormalizer::ALLOW_INVALID_VALUES => true, + 'not_normalizable_value_exceptions' => [], + ]; + + $this->normalizer->denormalize('invalid-value', StringBackedEnumDummy::class, null, $context); + } + + public function testDenormalizeNullWithAllowInvalidAndCollectErrorsThrows() + { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('The data is neither an integer nor a string'); + + $context = [ + BackedEnumNormalizer::ALLOW_INVALID_VALUES => true, + 'not_normalizable_value_exceptions' => [], // Indicate that we want to collect errors + ]; + + $this->normalizer->denormalize(null, StringBackedEnumDummy::class, null, $context); + } + public function testNormalizeShouldThrowExceptionForNonEnumObjects() { $this->expectException(\InvalidArgumentException::class); @@ -126,30 +177,4 @@ public function testItUsesTryFromIfContextIsPassed() $this->assertSame(StringBackedEnumDummy::GET, $this->normalizer->denormalize('GET', StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); } - - public function testDenormalizeNullWithAllowInvalidAndCollectErrorsThrows() - { - $this->expectException(NotNormalizableValueException::class); - $this->expectExceptionMessage('The data is neither an integer nor a string'); - - $context = [ - BackedEnumNormalizer::ALLOW_INVALID_VALUES => true, - 'not_normalizable_value_exceptions' => [], // Indicate that we want to collect errors - ]; - - $this->normalizer->denormalize(null, StringBackedEnumDummy::class, null, $context); - } - - public function testDenormalizeInvalidValueWithAllowInvalidAndCollectErrorsThrows() - { - $this->expectException(NotNormalizableValueException::class); - $this->expectExceptionMessage('The data must belong to a backed enumeration of type'); - - $context = [ - BackedEnumNormalizer::ALLOW_INVALID_VALUES => true, - 'not_normalizable_value_exceptions' => [], - ]; - - $this->normalizer->denormalize('invalid-value', StringBackedEnumDummy::class, null, $context); - } } diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index dbfdec22ec7..f297b81a796 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -118,7 +118,7 @@ public static function provideNormalizeCallbacks() return [ 'Change a string' => [ [ - 'bar' => function ($bar) { + 'bar' => static function ($bar) { static::assertEquals('baz', $bar); return 'baz'; @@ -129,7 +129,7 @@ public static function provideNormalizeCallbacks() ], 'Null an item' => [ [ - 'bar' => function ($value, $object, $attributeName, $format, $context) { + 'bar' => static function ($value, $object, $attributeName, $format, $context) { static::assertSame('baz', $value); static::assertInstanceOf(CallbacksObject::class, $object); static::assertSame('bar', $attributeName); @@ -142,7 +142,7 @@ public static function provideNormalizeCallbacks() ], 'Format a date' => [ [ - 'bar' => function ($bar) { + 'bar' => static function ($bar) { static::assertInstanceOf(\DateTimeImmutable::class, $bar); return $bar->format('d-m-Y H:i:s'); @@ -153,7 +153,7 @@ public static function provideNormalizeCallbacks() ], 'Collect a property' => [ [ - 'bar' => function (array $bars) { + 'bar' => static function (array $bars) { $result = ''; foreach ($bars as $bar) { $result .= $bar->bar; @@ -167,7 +167,7 @@ public static function provideNormalizeCallbacks() ], 'Count a property' => [ [ - 'bar' => fn (array $bars) => \count($bars), + 'bar' => static fn (array $bars) => \count($bars), ], [new CallbacksObject(), new CallbacksObject()], ['bar' => 2, 'foo' => null], @@ -180,7 +180,7 @@ public static function provideDenormalizeCallbacks(): array return [ 'Change a string' => [ [ - 'bar' => function ($bar) { + 'bar' => static function ($bar) { static::assertEquals('bar', $bar); return $bar; @@ -191,7 +191,7 @@ public static function provideDenormalizeCallbacks(): array ], 'Null an item' => [ [ - 'bar' => function ($value, $object, $attributeName, $format, $context) { + 'bar' => static function ($value, $object, $attributeName, $format, $context) { static::assertSame('baz', $value); static::assertTrue(is_a($object, CallbacksObject::class, true)); static::assertSame('bar', $attributeName); @@ -204,7 +204,7 @@ public static function provideDenormalizeCallbacks(): array ], 'Format a date' => [ [ - 'bar' => function ($bar) { + 'bar' => static function ($bar) { static::assertIsString($bar); return \DateTimeImmutable::createFromFormat('d-m-Y H:i:s', $bar); @@ -215,7 +215,7 @@ public static function provideDenormalizeCallbacks(): array ], 'Collect a property' => [ [ - 'bar' => function (array $bars) { + 'bar' => static function (array $bars) { $result = ''; foreach ($bars as $bar) { $result .= $bar->bar; @@ -229,7 +229,7 @@ public static function provideDenormalizeCallbacks(): array ], 'Count a property' => [ [ - 'bar' => fn (array $bars) => \count($bars), + 'bar' => static fn (array $bars) => \count($bars), ], [new CallbacksObject(), new CallbacksObject()], new CallbacksObject(2), @@ -242,7 +242,7 @@ public static function providerDenormalizeCallbacksWithTypedProperty(): array return [ 'Change a typed string' => [ [ - 'foo' => function ($foo) { + 'foo' => static function ($foo) { static::assertEquals('foo', $foo); return $foo; @@ -253,7 +253,7 @@ public static function providerDenormalizeCallbacksWithTypedProperty(): array ], 'Null an typed item' => [ [ - 'foo' => function ($value, $object, $attributeName, $format, $context) { + 'foo' => static function ($value, $object, $attributeName, $format, $context) { static::assertSame('fool', $value); static::assertTrue(is_a($object, CallbacksObject::class, true)); static::assertSame('foo', $attributeName); diff --git a/Tests/Normalizer/JsonSerializableNormalizerTest.php b/Tests/Normalizer/JsonSerializableNormalizerTest.php index 39dce311e20..c4d5dd0d946 100644 --- a/Tests/Normalizer/JsonSerializableNormalizerTest.php +++ b/Tests/Normalizer/JsonSerializableNormalizerTest.php @@ -66,7 +66,7 @@ public function testCircularNormalize() $serializer ->expects($this->once()) ->method('normalize') - ->willReturnCallback(function ($data, $format, $context) use ($normalizer) { + ->willReturnCallback(static function ($data, $format, $context) use ($normalizer) { $normalizer->normalize($data['qux'], $format, $context); return 'string_object'; diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 205e38d3eac..398c3fb964c 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -740,7 +740,7 @@ public function testNormalizeNotSerializableContext() 'go' => null, ]; - $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, ['not_serializable' => function () { + $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, ['not_serializable' => static function () { }])); } @@ -870,7 +870,7 @@ public function testDefaultObjectClassResolver() public function testObjectClassResolver() { - $classResolver = fn ($object) => ObjectDummy::class; + $classResolver = static fn ($object) => ObjectDummy::class; $normalizer = new ObjectNormalizer(null, null, null, null, null, $classResolver); @@ -1368,6 +1368,15 @@ public function testSkipVoidNeverReturnTypeAccessors() $this->assertArrayNotHasKey('neverProperty', $normalized); $this->assertEquals('value', $normalized['normalProperty']); } + + public function testMetadataIsAppliedToTheRightValue() + { + $obj = new ObjectWithMetadata(); + $normalizer = new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader())); + $normalized = $normalizer->normalize($obj); + + $this->assertSame(['name' => 'John', 'foo' => 42, 'hello' => 'Hello i am John'], $normalized); + } } class ProxyObjectDummy extends ObjectDummy @@ -2008,3 +2017,36 @@ public function __construct( ) { } } + +class ObjectWithMetadata +{ + private int $foo; + private string $name; + + public function __construct() + { + $this->foo = 42; + $this->name = 'John'; + } + + public function getName(): string + { + return $this->name; + } + + public function getFoo(): int + { + return $this->foo; + } + + #[Ignore] + public function isEqualTo(self $test): bool + { + return $this->name === $test->getName(); + } + + public function getHello(): string + { + return 'Hello i am '.$this->getName(); + } +} diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index c9b1bd7ec5c..d1c1feaa804 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -80,6 +80,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithOptionalConstructorParameter; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; +use Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\TrueBuiltInDummy; use Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor; @@ -177,7 +178,7 @@ public function testNormalizeWithSupportOnData() $normalizer1 = $this->createStub(NormalizerInterface::class); $normalizer1->method('getSupportedTypes')->willReturn(['*' => false]); $normalizer1->method('supportsNormalization') - ->willReturnCallback(fn ($data, $format) => isset($data->test)); + ->willReturnCallback(static fn ($data, $format) => isset($data->test)); $normalizer1->method('normalize')->willReturn('test1'); $normalizer2 = $this->createStub(NormalizerInterface::class); @@ -200,7 +201,7 @@ public function testDenormalizeWithSupportOnData() $denormalizer1 = $this->createStub(DenormalizerInterface::class); $denormalizer1->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer1->method('supportsDenormalization') - ->willReturnCallback(fn ($data, $type, $format) => isset($data['test1'])); + ->willReturnCallback(static fn ($data, $type, $format) => isset($data['test1'])); $denormalizer1->method('denormalize')->willReturn('test1'); $denormalizer2 = $this->createStub(DenormalizerInterface::class); @@ -991,7 +992,7 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet $this->assertInstanceOf(Php74Full::class, $th->getData()); - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), 'path' => $e->getPath(), @@ -1203,7 +1204,7 @@ public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMe $this->assertInstanceOf(Php74Full::class, $th->getData()[0]); $this->assertInstanceOf(Php74Full::class, $th->getData()[1]); - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), 'path' => $e->getPath(), @@ -1258,7 +1259,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() $this->assertInstanceOf(Php74Full::class, $th->getData()); - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), 'path' => $e->getPath(), @@ -1325,7 +1326,7 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa $this->assertInstanceOf(Php80WithPromotedTypedConstructor::class, $th->getData()); - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), 'path' => $e->getPath(), @@ -1393,7 +1394,7 @@ public function testCollectDenormalizationErrorsWithInvalidConstructorTypes() $this->assertTrue($object->bool); $this->assertSame(1, $object->int); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { + $exceptionsAsArray = array_map(static function (NotNormalizableValueException $e): array { return [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), @@ -1449,7 +1450,7 @@ public function testCollectDenormalizationErrorsWithUnionConstructorTypes() $this->assertInstanceOf(PartialDenormalizationException::class, $th); } - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), 'path' => $e->getPath(), @@ -1499,7 +1500,7 @@ public function testCollectDenormalizationErrorsWithEnumConstructor() $this->assertInstanceOf(PartialDenormalizationException::class, $th); } - $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + $exceptionsAsArray = array_map(static fn (NotNormalizableValueException $e): array => [ 'currentType' => $e->getCurrentType(), 'useMessageForUser' => $e->canUseMessageForUser(), 'message' => $e->getMessage(), @@ -1538,7 +1539,7 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc $this->assertInstanceOf(PartialDenormalizationException::class, $e); } - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { + $exceptionsAsArray = array_map(static function (NotNormalizableValueException $e): array { return [ 'currentType' => $e->getCurrentType(), 'useMessageForUser' => $e->canUseMessageForUser(), @@ -1548,9 +1549,9 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc $expected = [ [ - 'currentType' => 'string', + 'currentType' => StringBackedEnumDummy::class, 'useMessageForUser' => true, - 'message' => 'The data must belong to a backed enumeration of type Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy', + 'message' => "The data must be one of the following values: 'GET', 'OPTIONS'", ], ]; @@ -1704,7 +1705,7 @@ public function testPartialDenormalizationWithMissingConstructorTypes() $this->assertFalse(isset($object->two)); $this->assertSame('three string', $object->three); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { + $exceptionsAsArray = array_map(static function (NotNormalizableValueException $e): array { return [ 'currentType' => $e->getCurrentType(), 'expectedTypes' => $e->getExpectedTypes(), @@ -1785,7 +1786,7 @@ public function testDenormalizationFailsWithMultipleErrorsInDefaultContext() $this->assertIsArray($e->getErrors()); $this->assertCount(2, $e->getErrors(), 'Expected two denormalization errors'); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $ex): array { + $exceptionsAsArray = array_map(static function (NotNormalizableValueException $ex): array { return [ 'currentType' => $ex->getCurrentType(), 'expectedTypes' => $ex->getExpectedTypes(), diff --git a/composer.json b/composer.json index 8b22b980d19..57a47deff5f 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", "symfony/cache": "^7.4|^8.0", @@ -45,8 +45,8 @@ "symfony/yaml": "^7.4|^8.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/property-info": "<7.3" }, "autoload": {