8000 [Serializer][PropertyInfo][Validator] TypeInfo 7.2 compatibility · symfony/symfony@45d3ad2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 45d3ad2

Browse files
committed
[Serializer][PropertyInfo][Validator] TypeInfo 7.2 compatibility
1 parent 75fa917 commit 45d3ad2

File tree

7 files changed

+185
-40
lines changed

7 files changed

+185
-40
lines changed

src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait;
2828
use Symfony\Component\PropertyInfo\Type as LegacyType;
2929
use Symfony\Component\TypeInfo\Type;
30+
use Symfony\Component\TypeInfo\Type\NullableType;
3031

3132
/**
3233
* @author Kévin Dunglas <dunglas@gmail.com>
@@ -562,7 +563,14 @@ public static function typeProvider(): iterable
562563
yield ['f', Type::list(Type::object(\DateTimeImmutable::class)), null, null];
563564
yield ['g', Type::nullable(Type::array()), 'Nullable array.', null];
564565
yield ['h', Type::nullable(Type::string()), null, null];
565-
yield ['i', Type::union(Type::int(), Type::string(), Type::null()), null, null];
566+
567+
// BC layer for type-info < 7.2
568+
if (!class_exists(NullableType::class)) {
569+
yield ['i', Type::union(Type::int(), Type::string(), Type::null()), null, null];
570+
} else {
571+
yield ['i', Type::nullable(Type::union(Type::int(), Type::string())), null, null];
572+
}
573+
566574
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class)), null, null];
567575
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int())), null, null];
568576
yield ['donotexist', null, null, null];
@@ -629,7 +637,14 @@ public static function typeWithNoPrefixesProvider()
629637
yield ['f', null];
630638
yield ['g', Type::nullable(Type::array())];
631639
yield ['h', Type::nullable(Type::string())];
632-
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
640+
641+
// BC layer for type-info < 7.2
642+
if (!class_exists(NullableType::class)) {
643+
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
644+
} else {
645+
yield ['i', Type::nullable(Type::union(Type::int(), Type::string()))];
646+
}
647+
633648
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
634649
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))];
635650
yield ['donotexist', null];
@@ -693,7 +708,14 @@ public static function typeWithCustomPrefixesProvider(): iterable
693708
yield ['f', Type::list(Type::object(\DateTimeImmutable::class))];
694709
yield ['g', Type::nullable(Type::array())];
695710
yield ['h', Type::nullable(Type::string())];
696-
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
711+
712+
// BC layer for type-info < 7.2
713+
if (!class_exists(NullableType::class)) {
714+
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
715+
} else {
716+
yield ['i', Type::nullable(Type::union(Type::int(), Type::string()))];
717+
}
718+
697719
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
698720
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))];
699721
yield ['nonNullableCollectionOfNullableElements', Type::list(Type::nullable(Type::int()))];

src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use Symfony\Component\PropertyInfo\Type as LegacyType;
3737
use Symfony\Component\TypeInfo\Exception\LogicException;
3838
use Symfony\Component\TypeInfo\Type;
39+
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
3940

4041
require_once __DIR__.'/../Fixtures/Extractor/DummyNamespace.php';
4142

@@ -869,7 +870,14 @@ public function testPseudoTypes(string $property, ?Type $type)
869870
public static function pseudoTypesProvider(): iterable
870871
{
871872
yield ['classString', Type::string()];
872-
yield ['classStringGeneric', Type::generic(Type::string(), Type::object(\stdClass::class))];
873+
874+
// BC layer for type-info < 7.2
875+
if (!interface_exists(WrappingTypeInterface::class)) {
876+
yield ['classStringGeneric', Type::generic(Type::string(), Type::object(\stdClass::class))];
877+
} else {
878+
yield ['classStringGeneric', Type::string()];
879+
}
880+
873881
yield ['htmlEscapedString', Type::string()];
874882
yield ['lowercaseString', Type::string()];
875883
yield ['nonEmptyLowercaseString', Type::string()];

src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy;
3434
use Symfony\Component\PropertyInfo\Type as LegacyType;
3535
use Symfony\Component\TypeInfo\Type;
36+
use Symfony\Component\TypeInfo\Type\NullableType;
3637
use Symfony\Component\TypeInfo\TypeResolver\PhpDocAwareReflectionTypeResolver;
3738

3839
/**
@@ -772,7 +773,14 @@ public static function php80TypesProvider(): iterable
772773
yield ['foo', Type::nullable(Type::array())];
773774
yield ['bar', Type::nullable(Type::int())];
774775
yield ['timeout', Type::union(Type::int(), Type::float())];
775-
yield ['optional', Type::union(Type::nullable(Type::int()), Type::nullable(Type::float()))];
776+
777+
// BC layer for type-info < 7.2
778+
if (!class_exists(NullableType::class)) {
779+
yield ['optional', Type::union(Type::nullable(Type::int()), Type::nullable(Type::float()))];
780+
} else {
781+
yield ['optional', Type::nullable(Type::union(Type::float(), Type::int()))];
782+
}
783+
776784
yield ['string', Type::union(Type::string(), Type::object(\Stringable::class))];
777785
yield ['payload', Type::mixed()];
778786
yield ['data', Type::mixed()];

src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ public function getType(DocType $varType): ?Type
128128
$nullable = true;
129129
}
130130

131-
return $this->createType($varType, $nullable);
131+
$type = $this->createType($varType);
132+
133+
return $nullable ? Type::nullable($type) : $type;
132134
}
133135

134136
$varTypes = [];
@@ -156,8 +158,7 @@ public function getType(DocType $varType): ?Type
156158

157159
$unionTypes = [];
158160
foreach ($varTypes as $varType) {
159-
$t = $this->createType($varType, $nullable);
160-
if (null !== $t) {
161+
if (null !== $t = $this->createType($varType)) {
161162
$unionTypes[] = $t;
162163
}
163164
}
@@ -238,7 +239,7 @@ private function createLegacyType(DocType $type, bool $nullable): ?LegacyType
238239
/**
239240
* Creates a {@see Type} from a PHPDoc type.
240241
*/
241-
private function createType(DocType $docType, bool $nullable): ?Type
242+
private function createType(DocType $docType): ?Type
242243
{
243244
$docTypeString = (string) $docType;
244245

@@ -262,9 +263,8 @@ private function createType(DocType $docType, bool $nullable): ?Type
262263
}
263264

264265
$type = null !== $class ? Type::object($class) : Type::builtin($phpType);
265-
$type = Type::collection($type, ...$variableTypes);
266266

267-
return $nullable ? Type::nullable($type) : $type;
267+
return Type::collection($type, ...$variableTypes);
268268
}
269269

270270
if (!$docTypeString) {
@@ -277,9 +277,8 @@ private function createType(DocType $docType, bool $nullable): ?Type
277277

278278
if (str_starts_with($docTypeString, 'list<') && $docType instanceof Array_) {
279279
$collectionValueType = $this->getType($docType->getValueType());
280-
$type = Type::list($collectionValueType);
281280

282-
return $nullable ? Type::nullable($type) : $type;
281+
return Type::list($collectionValueType);
283282
}
284283

285284
if (str_starts_with($docTypeString, 'array<') && $docType instanceof Array_) {
@@ -288,16 +287,14 @@ private function createType(DocType $docType, bool $nullable): ?Type
288287
$collectionKeyType = $this->getType($docType->getKeyType());
289288
$collectionValueType = $this->getType($docType->getValueType());
290289

291-
$type = Type::array($collectionValueType, $collectionKeyType);
292-
293-
return $nullable ? Type::nullable($type) : $type;
290+
return Type::array($collectionValueType, $collectionKeyType);
294291
}
295292

296293
if ($docType instanceof PseudoType) {
297294
if ($docType->underlyingType() instanceof Integer) {
298-
return $nullable ? Type::nullable(Type::int()) : Type::int();
295+
return Type::int();
299296
} elseif ($docType->underlyingType() instanceof String_) {
300-
return $nullable ? Type::nullable(Type::string()) : Type::string();
297+
return Type::string();
301298
}
302299
}
303300

@@ -314,12 +311,10 @@ private function createType(DocType $docType, bool $nullable): ?Type
314311
[$phpType, $class] = $this->getPhpTypeAndClass($docTypeString);
315312

316313
if ('array' === $docTypeString) {
317-
return $nullable ? Type::nullable(Type::array()) : Type::array();
314+
return Type::array();
318315
}
319316

320-
$type = null !== $class ? Type::object($class) : Type::builtin($phpType);
321-
322-
return $nullable ? Type::nullable($type) : $type;
317+
return null !== $class ? Type::object($class) : Type::builtin($phpType);
323318
}
324319

325320
private function normalizeType(string $docType): string

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@
3434
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
3535
use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException;
3636
use Symfony\Component\TypeInfo\Type;
37+
use Symfony\Component\TypeInfo\Type\BuiltinType;
3738
use Symfony\Component\TypeInfo\Type\CollectionType;
3839
use Symfony\Component\TypeInfo\Type\IntersectionType;
40+
use Symfony\Component\TypeInfo\Type\NullableType;
3941
use Symfony\Component\TypeInfo\Type\ObjectType;
4042
use Symfony\Component\TypeInfo\Type\UnionType;
43+
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
4144
use Symfony\Component\TypeInfo\TypeIdentifier;
4245

4346
/**
@@ -644,7 +647,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass
644647
private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed
645648
{
646649
$expectedTypes = [];
647-
$isUnionType = $type->asNonNullable() instanceof UnionType;
650+
651+
// BC layer for type-info < 7.2
652+
if (method_exists(Type::class, 'asNonNullable')) {
653+
$isUnionType = $type->asNonNullable() instanceof UnionType;
654+
} else {
655+
$isUnionType = $type instanceof UnionType;
656+
}
657+
648658
$e = null;
649659
$extraAttributesException = null;
650660
$missingConstructorArgumentsException = null;
@@ -667,12 +677,23 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
667677
$collectionValueType = $t->getCollectionValueType();
668678
}
669679

670-
$t = $t->getBaseType();
680+
// BC layer for type-info < 7.2
681+
if (method_exists(Type::class, 'getBaseType')) {
682+
$t = $t->getBaseType();
683+
} else {
684+
while ($t instanceof WrappingTypeInterface) {
685+
$t = $t->getWrappedType();
686+
}
687+
}
671688

672689
// Fix a collection that contains the only one element
673690
// This is special to xml format only
674-
if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) {
675-
$data = [$data];
691+
if ('xml' === $format && $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) {
692+
// BC layer for type-info < 7.2
693+
$isMixedType = method_exists(Type::class, 'isA') ? $collectionValueType->isA(TypeIdentifier::MIXED) : $collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED);
694+
if (!$isMixedType) {
695+
$data = [$data];
696+
}
676697
}
677698

678699
// This try-catch should cover all NotNormalizableValueException (and all return branches after the first
@@ -695,7 +716,10 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
695716
return '';
696717
}
697718

698-
$isNullable = $isNullable ?: $type->isNullable();
719+
// BC layer for type-info < 7.2
720+
if (method_exists(Type::class, 'isNullable')) {
721+
$isNullable = $isNullable ?: $type->isNullable();
722+
}
699723
}
700724

701725
switch ($typeIdentifier) {
@@ -732,7 +756,16 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
732756

733757
if ($collectionValueType) {
734758
try {
735-
$collectionValueBaseType = $collectionValueType->getBaseType();
759+
$collectionValueBaseType = $collectionValueType;
760+
761+
// BC layer for type-info < 7.2
762+
if (!interface_exists(WrappingTypeInterface::class)) {
763+
$collectionValueBaseType = $collectionValueType->getBaseType();
764+
} else {
765+
while ($collectionValueBaseType instanceof WrappingTypeInterface) {
766+
$collectionValueBaseType = $collectionValueBaseType->getWrappedType();
767+
}
768+
}
736769
} catch (TypeInfoLogicException) {
737770
$collectionValueBaseType = Type::mixed();
738771
}
@@ -742,15 +775,29 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
742775
$class = $collectionValueBaseType->getClassName().'[]';
743776
$context['key_type'] = $collectionKeyType;
744777
$context['value_type'] = $collectionValueType;
745-
} elseif (TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) {
778+
} elseif (
779+
// BC layer for type-info < 7.2
780+
!class_exists(NullableType::class) && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()
781+
|| $collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()
782+
) {
746783
// get inner type for any nested array
747784
$innerType = $collectionValueType;
785+
if ($innerType instanceof NullableType) {
786+
$innerType = $innerType->getWrappedType();
787+
}
748788

749789
// note that it will break for any other builtinType
750790
$dimensions = '[]';
751791
while ($innerType instanceof CollectionType) {
752792
$dimensions .= '[]';
753793
$innerType = $innerType->getCollectionValueType();
794+
if ($innerType instanceof NullableType) {
795+
$innerType = $innerType->getWrappedType();
796+
}
797+
}
798+
799+
while ($innerType instanceof WrappingTypeInterface) {
800+
$innerType = $innerType->getWrappedType();
754801
}
755802

756803
if ($innerType instanceof ObjectType) {
@@ -862,8 +909,15 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
862909
throw $missingConstructorArgumentsException;
863910
}
864911

865-
if (!$isUnionType && $e) {
866-
throw $e;
912+
// BC layer for type-info < 7.2
913+
if (!class_exists(NullableType::class)) {
914+
if (!$isUnionType && $e) {
915+
throw $e;
916+
}
917+
} else {
918+
if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) {
919+
throw $e;
920+
}
867921
}
868922

869923
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {

src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1717
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1818
use Symfony\Component\TypeInfo\Type;
19+
use Symfony\Component\TypeInfo\Type\BuiltinType;
1920
use Symfony\Component\TypeInfo\Type\UnionType;
21+
use Symfony\Component\TypeInfo\TypeIdentifier;
2022

2123
/**
2224
* Denormalizes arrays of objects.
@@ -59,7 +61,15 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
5961
$typeIdentifiers = [];
6062
if (null !== $keyType = ($context['key_type'] ?? null)) {
6163
if ($keyType instanceof Type) {
62-
$typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]);
64+
// BC layer for type-info < 7.2
65+
if (method_exists(Type::class, 'getBaseType')) {
66+
$typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]);
67+
} else {
68+
/** @var list<BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>> */
69+
$keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType];
70+
71+
$typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes);
72+
}
6373
} else {
6474
$typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]);
6575
}

0 commit comments

Comments
 (0)
0