Skip to content

Commit 108cfc0

Browse files
committed
feature #53160 [PropertyInfo] Deprecate PropertyInfo Type (mtarld)
This PR was merged into the 7.1 branch. Discussion ---------- [PropertyInfo] Deprecate PropertyInfo Type | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | no | Deprecations? | yes | Issues | | License | MIT This PR is a follow-up of symfony/symfony#52510. As the TypeInfo's `Type` aims to represent types in the Symfony ecosystem, the PropertyInfo's `Type` needs to be deprecated in favor of the first one. Commits ------- d32e81c816 [PropertyInfo] Deprecate PropertyInfo Type
2 parents bdcae58 + b8d7dfe commit 108cfc0

9 files changed

+373
-94
lines changed

Exception/NotNormalizableValueException.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ class NotNormalizableValueException extends UnexpectedValueException
2222
private bool $useMessageForUser = false;
2323

2424
/**
25-
* @param string[] $expectedTypes
26-
* @param bool $useMessageForUser If the message passed to this exception is something that can be shown
27-
* safely to your user. In other words, avoid catching other exceptions and
28-
* passing their message directly to this class.
25+
* @param list<string|\Stringable> $expectedTypes
26+
* @param bool $useMessageForUser If the message passed to this exception is something that can be shown
27+
* safely to your user. In other words, avoid catching other exceptions and
28+
* passing their message directly to this class.
2929
*/
3030
public static function createForUnexpectedDataType(string $message, mixed $data, array $expectedTypes, ?string $path = null, bool $useMessageForUser = false, int $code = 0, ?\Throwable $previous = null): self
3131
{
3232
$self = new self($message, $code, $previous);
3333

3434
$self->currentType = get_debug_type($data);
35-
$self->expectedTypes = $expectedTypes;
35+
$self->expectedTypes = array_map(strval(...), $expectedTypes);
3636
$self->path = $path;
3737
$self->useMessageForUser = $useMessageForUser;
3838

Normalizer/AbstractObjectNormalizer.php

Lines changed: 275 additions & 41 deletions
Large diffs are not rendered by default.

Normalizer/ArrayDenormalizer.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\Type;
14+
use Symfony\Component\PropertyInfo\Type as LegacyType;
1515
use Symfony\Component\Serializer\Exception\BadMethodCallException;
1616
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1717
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
18+
use Symfony\Component\TypeInfo\Type;
19+
use Symfony\Component\TypeInfo\Type\UnionType;
1820

1921
/**
2022
* Denormalizes arrays of objects.
@@ -46,23 +48,28 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
4648
throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!');
4749
}
4850
if (!\is_array($data)) {
49-
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, [Type::BUILTIN_TYPE_ARRAY], $context['deserialization_path'] ?? null);
51+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null);
5052
}
5153
if (!str_ends_with($type, '[]')) {
5254
throw new InvalidArgumentException('Unsupported class: '.$type);
5355
}
5456

5557
$type = substr($type, 0, -2);
5658

57-
$builtinTypes = array_map(static function (Type $keyType) {
58-
return $keyType->getBuiltinType();
59-
}, \is_array($keyType = $context['key_type'] ?? []) ? $keyType : [$keyType]);
59+
$typeIdentifiers = [];
60+
if (null !== $keyType = ($context['key_type'] ?? null)) {
61+
if ($keyType instanceof Type) {
62+
$typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]);
63+
} else {
64+
$typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]);
65+
}
66+
}
6067

6168
foreach ($data as $key => $value) {
6269
$subContext = $context;
6370
$subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]";
6471

65-
$this->validateKeyType($builtinTypes, $key, $subContext['deserialization_path']);
72+
$this->validateKeyType($typeIdentifiers, $key, $subContext['deserialization_path']);
6673

6774
$data[$key] = $this->denormalizer->denormalize($value, $type, $format, $subContext);
6875
}
@@ -80,18 +87,21 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form
8087
&& $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context);
8188
}
8289

83-
private function validateKeyType(array $builtinTypes, mixed $key, string $path): void
90+
/**
91+
* @param list<string> $typeIdentifiers
92+
*/
93+
private function validateKeyType(array $typeIdentifiers, mixed $key, string $path): void
8494
{
85-
if (!$builtinTypes) {
95+
if (!$typeIdentifiers) {
8696
return;
8797
}
8898

89-
foreach ($builtinTypes as $builtinType) {
90-
if (('is_'.$builtinType)($key)) {
99+
foreach ($typeIdentifiers as $typeIdentifier) {
100+
if (('is_'.$typeIdentifier)($key)) {
91101
return;
92102
}
93103
}
94104

95-
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $builtinTypes), get_debug_type($key)), $key, $builtinTypes, $path, true);
105+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true);
96106
}
97107
}

Normalizer/BackedEnumNormalizer.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\Type;
1514
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1615
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1716

@@ -70,7 +69,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
7069
}
7170

7271
if (!\is_int($data) && !\is_string($data)) {
73-
throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
72+
throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, ['int', 'string'], $context['deserialization_path'] ?? null, true);
7473
}
7574

7675
try {

Normalizer/DateTimeNormalizer.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\Type;
1514
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1615
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1716

@@ -104,7 +103,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
104103
}
105104

106105
if (!\is_string($data) || '' === trim($data)) {
107-
throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
106+
throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, ['string'], $context['deserialization_path'] ?? null, true);
108107
}
109108

110109
try {
@@ -122,7 +121,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
122121

123122
$dateTimeErrors = $type::getLastErrors();
124123

125-
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
124+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true);
126125
}
127126

128127
$defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null;
@@ -137,7 +136,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
137136
} catch (NotNormalizableValueException $e) {
138137
throw $e;
139138
} catch (\Exception $e) {
140-
throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, false, $e->getCode(), $e);
139+
throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, false, $e->getCode(), $e);
141140
}
142141
}
143142

Normalizer/DateTimeZoneNormalizer.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\Type;
1514
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1615
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1716

@@ -52,13 +51,13 @@ public function supportsNormalization(mixed $data, ?string $format = null, array
5251
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \DateTimeZone
5352
{
5453
if ('' === $data || null === $data) {
55-
throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
54+
throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, ['string'], $context['deserialization_path'] ?? null, true);
5655
}
5756

5857
try {
5958
return new \DateTimeZone($data);
6059
} catch (\Exception $e) {
61-
throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
60+
throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
6261
}
6362
}
6463

Normalizer/UidNormalizer.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\Type;
1514
use Symfony\Component\Serializer\Exception\LogicException;
1615
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1716
use Symfony\Component\Uid\AbstractUid;
@@ -71,7 +70,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
7170
try {
7271
return $type::fromString($data);
7372
} catch (\InvalidArgumentException|\TypeError) {
74-
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
73+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true);
7574
}
7675
}
7776

Tests/Normalizer/AbstractObjectNormalizerTest.php

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1616
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1717
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
18-
use Symfony\Component\PropertyInfo\Type;
18+
use Symfony\Component\PropertyInfo\Type as LegacyType;
1919
use Symfony\Component\Serializer\Attribute\Context;
2020
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
2121
use Symfony\Component\Serializer\Attribute\SerializedName;
@@ -56,6 +56,7 @@
5656
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull;
5757
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithStringObject;
5858
use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute;
59+
use Symfony\Component\TypeInfo\Type;
5960

6061
class AbstractObjectNormalizerTest extends TestCase
6162
{
@@ -433,11 +434,20 @@ public function testDenormalizeCollectionDecodedFromXmlWithTwoChildren()
433434
private function getDenormalizerForDummyCollection()
434435
{
435436
$extractor = $this->createMock(PhpDocExtractor::class);
436-
$extractor->method('getTypes')
437-
->will($this->onConsecutiveCalls(
438-
[new Type('array', false, null, true, new Type('int'), new Type('object', false, DummyChild::class))],
439-
null
440-
));
437+
438+
if (method_exists(PhpDocExtractor::class, 'getType')) {
439+
$extractor->method('getType')
440+
->will($this->onConsecutiveCalls(
441+
Type::list(Type::object(DummyChild::class)),
442+
null,
443+
));
444+
} else {
445+
$extractor->method('getTypes')
446+
->will($this->onConsecutiveCalls(
447+
[new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('object', false, DummyChild::class))],
448+
null
449+
));
450+
}
441451

442452
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
443453
$arrayDenormalizer = new ArrayDenormalizerDummy();
@@ -488,11 +498,20 @@ public function testDenormalizeNotSerializableObjectToPopulate()
488498
private function getDenormalizerForStringCollection()
489499
{
490500
$extractor = $this->createMock(PhpDocExtractor::class);
491-
$extractor->method('getTypes')
492-
->will($this->onConsecutiveCalls(
493-
[new Type('array', false, null, true, new Type('int'), new Type('string'))],
494-
null
495-
));
501+
502+
if (method_exists(PhpDocExtractor::class, 'getType')) {
503+
$extractor->method('getType')
504+
->will($this->onConsecutiveCalls(
505+
Type::list(Type::string()),
506+
null,
507+
));
508+
} else {
509+
$extractor->method('getTypes')
510+
->will($this->onConsecutiveCalls(
511+
[new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('string'))],
512+
null
513+
));
514+
}
496515

497516
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
498517
$arrayDenormalizer = new ArrayDenormalizerDummy();
@@ -675,21 +694,40 @@ public function testDenormalizeBasicTypePropertiesFromXml()
675694
private function getDenormalizerForObjectWithBasicProperties()
676695
{
677696
$extractor = $this->createMock(PhpDocExtractor::class);
678-
$extractor->method('getTypes')
679-
->will($this->onConsecutiveCalls(
680-
[new Type('bool')],
681-
[new Type('bool')],
682-
[new Type('bool')],
683-
[new Type('bool')],
684-
[new Type('int')],
685-
[new Type('int')],
686-
[new Type('float')],
687-
[new Type('float')],
688-
[new Type('float')],
689-
[new Type('float')],
690-
[new Type('float')],
691-
[new Type('float')]
692-
));
697+
698+
if (method_exists(PhpDocExtractor::class, 'getType')) {
699+
$extractor->method('getType')
700+
->will($this->onConsecutiveCalls(
701+
Type::bool(),
702+
Type::bool(),
703+
Type::bool(),
704+
Type::bool(),
705+
Type::int(),
706+
Type::int(),
707+
Type::float(),
708+
Type::float(),
709+
Type::float(),
710+
Type::float(),
711+
Type::float(),
712+
Type::float(),
713+
));
714+
} else {
715+
$extractor->method('getTypes')
716+
->will($this->onConsecutiveCalls(
717+
[new LegacyType('bool')],
718+
[new LegacyType('bool')],
719+
[new LegacyType('bool')],
720+
[new LegacyType('bool')],
721+
[new LegacyType('int')],
722+
[new LegacyType('int')],
723+
[new LegacyType('float')],
724+
[new LegacyType('float')],
725+
[new LegacyType('float')],
726+
[new LegacyType('float')],
727+
[new LegacyType('float')],
728+
[new LegacyType('float')]
729+
));
730+
}
693731

694732
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
695733
$arrayDenormalizer = new ArrayDenormalizerDummy();

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"symfony/property-access": "^6.4|^7.0",
3737
"symfony/property-info": "^6.4|^7.0",
3838
"symfony/translation-contracts": "^2.5|^3",
39+
"symfony/type-info": "^7.1",
3940
"symfony/uid": "^6.4|^7.0",
4041
"symfony/validator": "^6.4|^7.0",
4142
"symfony/var-dumper": "^6.4|^7.0",

0 commit comments

Comments
 (0)