Skip to content

Commit 76d2cc6

Browse files
feature #57630 [TypeInfo] Redesign Type methods and nullability (mtarld)
This PR was merged into the 7.2 branch. Discussion ---------- [TypeInfo] Redesign Type methods and nullability | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT By using the component and trying to integrate it within several libs, I ended up finding some limitations due to the component design. Basically, the issues were: - Abstract classes (such as `Type`) knowing its concrete implementations (such as in `getBaseType` method). - For API simplicity `CollectionType`, and `GenericType` are forwarding methods to their wrapped type using `__call`, but I think this must be avoided as much as possible. - It is difficult to know if a type is "just" a nullable type (because we need to check the union content). - Invalid types could be instantiated To tackle these issues, I propose the following: - Introduce `CompositeTypeInterface` and `WrappingTypeInterface` that describe whether a type is composed by several types or wrapping one. - Add a `NullableType` which extends `UnionType` and is a `WrappingTypeInterface`, so that it is easy to recognize a nullable type and to get the non-nullable related type. - Remove the `Type::asNonNullable` method as it's covered by `NullableType::getWrappedType` - Get rid of the `__call` method as it is easy to know it a type is wrapping another one - Rename `is` to `satisfy` and add the new `CompositeTypeInterface::composedTypesSatisfy` and `WrappingTypeInterface::wrappedTypeSatisfy` methods - Rename `isA` to `isIdentifiedBy` Plus, **thanks to `@skalpa`'s great work**, the following checks are added to prevent invalid type construction: - `BackedEnumType` - Backed type can only be `int` or `string` - `CollectionType` - Main type can only be `array` or `iterable` when it's a built-in type - `GenericType` - Main type can only be `array` or `iterable` when it's a built-in type - `IntersectionType` - Cannot contain other types than class types - `UnionType` - Cannot contain `UnionType` - Cannot contain standalone types (`mixed`, `void`, and `never`) - Cannot contain either `true` and `false` - Cannot contain either `bool` and `true` or `false` - Cannot contain either `object` and a class type Commits ------- 8c376c89c9b [TypeInfo] Redesign Type methods and nullability
2 parents 456fe16 + 9cc24fc commit 76d2cc6

File tree

2 files changed

+24
-14
lines changed

2 files changed

+24
-14
lines changed

Mapping/Loader/PropertyInfoLoader.php

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1717
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
1818
use Symfony\Component\TypeInfo\Type as TypeInfoType;
19+
use Symfony\Component\TypeInfo\Type\BuiltinType;
1920
use Symfony\Component\TypeInfo\Type\CollectionType;
20-
use Symfony\Component\TypeInfo\Type\IntersectionType;
21+
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
22+
use Symfony\Component\TypeInfo\Type\NullableType;
2123
use Symfony\Component\TypeInfo\Type\ObjectType;
22-
use Symfony\Component\TypeInfo\Type\UnionType;
2324
use Symfony\Component\TypeInfo\TypeIdentifier;
25+
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
2426
use Symfony\Component\Validator\Constraints\All;
2527
use Symfony\Component\Validator\Constraints\NotBlank;
2628
use Symfony\Component\Validator\Constraints\NotNull;
@@ -139,11 +141,10 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
139141
}
140142

141143
$type = $types;
142-
$nullable = false;
144+
$nullable = $type->isNullable();
143145

144-
if ($type instanceof UnionType && $type->isNullable()) {
145-
$nullable = true;
146-
$type = $type->asNonNullable();
146+
if ($type instanceof NullableType) {
147+
$type = $type->getWrappedType();
147148
}
148149

149150
if ($type instanceof CollectionType) {
@@ -193,18 +194,27 @@ private function getTypeConstraintLegacy(string $builtinType, PropertyInfoType $
193194

194195
private function getTypeConstraint(TypeInfoType $type): ?Type
195196
{
196-
if ($type instanceof UnionType || $type instanceof IntersectionType) {
197-
return ($type->isA(TypeIdentifier::INT) || $type->isA(TypeIdentifier::FLOAT) || $type->isA(TypeIdentifier::STRING) || $type->isA(TypeIdentifier::BOOL)) ? new Type(['type' => 'scalar']) : null;
197+
if ($type instanceof CompositeTypeInterface) {
198+
return $type->isIdentifiedBy(
199+
TypeIdentifier::INT,
200+
TypeIdentifier::FLOAT,
201+
TypeIdentifier::STRING,
202+
TypeIdentifier::BOOL,
203+
TypeIdentifier::TRUE,
204+
TypeIdentifier::FALSE,
205+
) ? new Type(['type' => 'scalar']) : null;
198206
}
199207

200-
$baseType = $type->getBaseType();
208+
while ($type instanceof WrappingTypeInterface) {
209+
$type = $type->getWrappedType();
210+
}
201211

202-
if ($baseType instanceof ObjectType) {
203-
return new Type(['type' => $baseType->getClassName()]);
212+
if ($type instanceof ObjectType) {
213+
return new Type(['type' => $type->getClassName()]);
204214
}
205215

206-
if (TypeIdentifier::MIXED !== $baseType->getTypeIdentifier()) {
207-
return new Type(['type' => $baseType->getTypeIdentifier()->value]);
216+
if ($type instanceof BuiltinType && TypeIdentifier::MIXED !== $type->getTypeIdentifier()) {
217+
return new Type(['type' => $type->getTypeIdentifier()->value]);
208218
}
209219

210220
return null;

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"symfony/property-access": "^6.4|^7.0",
4040
"symfony/property-info": "^6.4|^7.0",
4141
"symfony/translation": "^6.4.3|^7.0.3",
42-
"symfony/type-info": "^7.1",
42+
"symfony/type-info": "^7.2",
4343
"egulias/email-validator": "^2.1.10|^3|^4"
4444
},
4545
"conflict": {

0 commit comments

Comments
 (0)