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)
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