Skip to content

[PropertyInfo][Serializer][TypeInfo][Validator] TypeInfo 7.1 compatibility #58885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Doctrine/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"symfony/security-core": "^6.4|^7.0",
"symfony/stopwatch": "^6.4|^7.0",
"symfony/translation": "^6.4|^7.0",
"symfony/type-info": "^7.2",
"symfony/type-info": "^7.1",
"symfony/uid": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"symfony/string": "^6.4|^7.0",
"symfony/translation": "^6.4|^7.0",
"symfony/twig-bundle": "^6.4|^7.0",
"symfony/type-info": "^7.2",
"symfony/type-info": "^7.1",
"symfony/validator": "^6.4|^7.0",
"symfony/workflow": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait;
use Symfony\Component\PropertyInfo\Type as LegacyType;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\NullableType;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
Expand Down Expand Up @@ -562,7 +563,14 @@ public static function typeProvider(): iterable
yield ['f', Type::list(Type::object(\DateTimeImmutable::class)), null, null];
yield ['g', Type::nullable(Type::array()), 'Nullable array.', null];
yield ['h', Type::nullable(Type::string()), null, null];
yield ['i', Type::union(Type::int(), Type::string(), Type::null()), null, null];

// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
yield ['i', Type::union(Type::int(), Type::string(), Type::null()), null, null];
} else {
yield ['i', Type::nullable(Type::union(Type::int(), Type::string())), null, null];
}

yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class)), null, null];
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int())), null, null];
yield ['donotexist', null, null, null];
Expand Down Expand Up @@ -629,7 +637,14 @@ public static function typeWithNoPrefixesProvider()
yield ['f', null];
yield ['g', Type::nullable(Type::array())];
yield ['h', Type::nullable(Type::string())];
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];

// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
} else {
yield ['i', Type::nullable(Type::union(Type::int(), Type::string()))];
}

yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))];
yield ['donotexist', null];
Expand Down Expand Up @@ -693,7 +708,14 @@ public static function typeWithCustomPrefixesProvider(): iterable
yield ['f', Type::list(Type::object(\DateTimeImmutable::class))];
yield ['g', Type::nullable(Type::array())];
yield ['h', Type::nullable(Type::string())];
yield ['i', Type::nullable(Type::union(Type::int(), Type::string()))];

// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
yield ['i', Type::union(Type::int(), Type::string(), Type::null())];
} else {
yield ['i', Type::nullable(Type::union(Type::int(), Type::string()))];
}

yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::list(Type::int()))];
yield ['nonNullableCollectionOfNullableElements', Type::list(Type::nullable(Type::int()))];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use Symfony\Component\PropertyInfo\Type as LegacyType;
use Symfony\Component\TypeInfo\Exception\LogicException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;

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

Expand Down Expand Up @@ -869,7 +870,14 @@ public function testPseudoTypes(string $property, ?Type $type)
public static function pseudoTypesProvider(): iterable
{
yield ['classString', Type::string()];
yield ['classStringGeneric', Type::string()];

// BC layer for type-info < 7.2
if (!interface_exists(WrappingTypeInterface::class)) {
yield ['classStringGeneric', Type::generic(Type::string(), Type::object(\stdClass::class))];
} else {
yield ['classStringGeneric', Type::string()];
}

yield ['htmlEscapedString', Type::string()];
yield ['lowercaseString', Type::string()];
yield ['nonEmptyLowercaseString', Type::string()];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy;
use Symfony\Component\PropertyInfo\Type as LegacyType;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\NullableType;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
Expand Down Expand Up @@ -771,7 +772,14 @@ public static function php80TypesProvider(): iterable
yield ['foo', Type::nullable(Type::array())];
yield ['bar', Type::nullable(Type::int())];
yield ['timeout', Type::union(Type::int(), Type::float())];
yield ['optional', Type::nullable(Type::union(Type::float(), Type::int()))];

// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
yield ['optional', Type::union(Type::nullable(Type::int()), Type::nullable(Type::float()))];
} else {
yield ['optional', Type::nullable(Type::union(Type::float(), Type::int()))];
}

yield ['string', Type::union(Type::string(), Type::object(\Stringable::class))];
yield ['payload', Type::mixed()];
yield ['data', Type::mixed()];
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/PropertyInfo/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"require": {
"php": ">=8.2",
"symfony/string": "^6.4|^7.0",
"symfony/type-info": "^7.2"
"symfony/type-info": "^7.1"
},
"require-dev": {
"symfony/serializer": "^6.4|^7.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
Expand Down Expand Up @@ -646,6 +647,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass
private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed
{
$expectedTypes = [];

// BC layer for type-info < 7.2
if (method_exists(Type::class, 'asNonNullable')) {
$isUnionType = $type->asNonNullable() instanceof UnionType;
} else {
$isUnionType = $type instanceof UnionType;
}

$e = null;
$extraAttributesException = null;
$missingConstructorArgumentsException = null;
Expand All @@ -667,14 +676,23 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
$collectionValueType = $t->getCollectionValueType();
}

while ($t instanceof WrappingTypeInterface) {
$t = $t->getWrappedType();
// BC layer for type-info < 7.2
if (method_exists(Type::class, 'getBaseType')) {
$t = $t->getBaseType();
} else {
while ($t instanceof WrappingTypeInterface) {
$t = $t->getWrappedType();
}
}

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

// This try-catch should cover all NotNormalizableValueException (and all return branches after the first
Expand Down Expand Up @@ -731,17 +749,31 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
}

if ($collectionValueType) {
$collectionValueBaseType = $collectionValueType;
while ($collectionValueBaseType instanceof WrappingTypeInterface) {
$collectionValueBaseType = $collectionValueBaseType->getWrappedType();
try {
$collectionValueBaseType = $collectionValueType;

// BC layer for type-info < 7.2
if (!interface_exists(WrappingTypeInterface::class)) {
$collectionValueBaseType = $collectionValueType->getBaseType();
} else {
while ($collectionValueBaseType instanceof WrappingTypeInterface) {
$collectionValueBaseType = $collectionValueBaseType->getWrappedType();
}
}
} catch (TypeInfoLogicException) {
$collectionValueBaseType = Type::mixed();
}

if ($collectionValueBaseType instanceof ObjectType) {
$typeIdentifier = TypeIdentifier::OBJECT;
$class = $collectionValueBaseType->getClassName().'[]';
$context['key_type'] = $collectionKeyType;
$context['value_type'] = $collectionValueType;
} elseif ($collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) {
} elseif (
// BC layer for type-info < 7.2
!class_exists(NullableType::class) && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()
|| $collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()
) {
// get inner type for any nested array
$innerType = $collectionValueType;
if ($innerType instanceof NullableType) {
Expand Down Expand Up @@ -871,8 +903,15 @@ private function validateAndDenormalize(Type $type, string $currentClass, string
throw $missingConstructorArgumentsException;
}

if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) {
throw $e;
// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
if (!$isUnionType && $e) {
throw $e;
}
} else {
if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) {
throw $e;
}
}

if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@
$typeIdentifiers = [];
if (null !== $keyType = ($context['key_type'] ?? null)) {
if ($keyType instanceof Type) {
/** @var list<BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>> */
$keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType];

$typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes);
// BC layer for type-info < 7.2
if (method_exists(Type::class, 'getBaseType')) {
$typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]);

Check failure on line 61 in src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php:61:78: UndefinedMethod: Method Symfony\Component\TypeInfo\Type::getBaseType does not exist (see https://psalm.dev/022)

Check failure on line 61 in src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php:61:78: UndefinedMethod: Method Symfony\Component\TypeInfo\Type::getBaseType does not exist (see https://psalm.dev/022)
} else {
/** @var list<BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>> */
$keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType];

$typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes);
}
} else {
$typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]);
}
Expand Down
3 changes: 1 addition & 2 deletions src/Symfony/Component/Serializer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"symfony/property-access": "^6.4|^7.0",
"symfony/property-info": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3",
"symfony/type-info": "^7.2",
"symfony/type-info": "^7.1",
"symfony/uid": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0",
Expand All @@ -51,7 +51,6 @@
"symfony/dependency-injection": "<6.4",
"symfony/property-access": "<6.4",
"symfony/property-info": "<6.4",
"symfony/type-info": "<7.2",
"symfony/uid": "<6.4",
"symfony/validator": "<6.4",
"symfony/yaml": "<6.4"
Expand Down
8 changes: 2 additions & 6 deletions src/Symfony/Component/TypeInfo/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,11 @@
},
"require-dev": {
"phpstan/phpdoc-parser": "^1.0|^2.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/property-info": "^7.2"
"symfony/dependency-injection": "^6.4|^7.0"
},
"conflict": {
"phpstan/phpdoc-parser": "<1.0",
"symfony/dependency-injection": "<6.4",
"symfony/property-info": ">=7.1,<7.1.9",
"symfony/serializer": ">=7.1,<7.1.9",
"symfony/validator": ">=7.1,<7.1.9"
"symfony/dependency-injection": "<6.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\TypeInfo\\": "" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
use Symfony\Component\TypeInfo\Type\IntersectionType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
Expand Down Expand Up @@ -141,7 +143,22 @@
}

$type = $types;
$nullable = $type->isNullable();

// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
$nullable = false;

if ($type instanceof UnionType && $type->isNullable()) {
$nullable = true;
$type = $type->asNonNullable();

Check failure on line 153 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:153:40: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\UnionType::asNonNullable does not exist (see https://psalm.dev/022)

Check failure on line 153 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:153:40: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\UnionType::asNonNullable does not exist (see https://psalm.dev/022)
}
} else {
$nullable = $type->isNullable();

if ($type instanceof NullableType) {
$type = $type->getWrappedType();
}
}

if ($type instanceof NullableType) {
$type = $type->getWrappedType();
Expand Down Expand Up @@ -194,6 +211,25 @@

private function getTypeConstraint(TypeInfoType $type): ?Type
{
// BC layer for type-info < 7.2
if (!interface_exists(CompositeTypeInterface::class)) {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return ($type->isA(TypeIdentifier::INT) || $type->isA(TypeIdentifier::FLOAT) || $type->isA(TypeIdentifier::STRING) || $type->isA(TypeIdentifier::BOOL)) ? new Type(['type' => 'scalar']) : null;

Check failure on line 217 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:217:32: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\IntersectionType::isA does not exist (see https://psalm.dev/022)

Check failure on line 217 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:217:67: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\IntersectionType::isA does not exist (see https://psalm.dev/022)

Check failure on line 217 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:217:104: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\IntersectionType::isA does not exist (see https://psalm.dev/022)

Check failure on line 217 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:217:142: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\IntersectionType::isA does not exist (see https://psalm.dev/022)

Check failure on line 217 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:217:32: UndefinedMethod: Method Symfony\Component\TypeInfo\Type\IntersectionType::isA does not exist (see https://psalm.dev/022)
}

$baseType = $type->getBaseType();

Check failure on line 220 in src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php:220:32: UndefinedMethod: Method Symfony\Component\TypeInfo\Type::getBaseType does not exist (see https://psalm.dev/022)

if ($baseType instanceof ObjectType) {
return new Type(['type' => $baseType->getClassName()]);
}

if (TypeIdentifier::MIXED !== $baseType->getTypeIdentifier()) {
return new Type(['type' => $baseType->getTypeIdentifier()->value]);
}

return null;
}

if ($type instanceof CompositeTypeInterface) {
return $type->isIdentifiedBy(
TypeIdentifier::INT,
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Validator/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"symfony/property-access": "^6.4|^7.0",
"symfony/property-info": "^6.4|^7.0",
"symfony/translation": "^6.4.3|^7.0.3",
"symfony/type-info": "^7.2-RC1",
"symfony/type-info": "^7.1",
"egulias/email-validator": "^2.1.10|^3|^4"
},
"conflict": {
Expand Down
Loading