• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 14500959057

16 Apr 2025 07:29PM UTC coverage: 8.532% (+0.3%) from 8.189%
14500959057

push

github

web-flow
feat: Use `Type` of `TypeInfo` instead of `PropertyInfo` (#6979)

Co-authored-by: soyuka <soyuka@users.noreply.github.com>

scopes: metadata, doctrine, json-schema

300 of 616 new or added lines in 25 files covered. (48.7%)

285 existing lines in 18 files now uncovered.

13513 of 158381 relevant lines covered (8.53%)

22.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.07
/src/Metadata/Util/PropertyInfoToTypeInfoHelper.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Metadata\Util;
15

16
use Symfony\Component\PropertyInfo\Type as LegacyType;
17
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
18
use Symfony\Component\TypeInfo\Type;
19
use Symfony\Component\TypeInfo\Type\BuiltinType;
20
use Symfony\Component\TypeInfo\Type\CollectionType;
21
use Symfony\Component\TypeInfo\Type\GenericType;
22
use Symfony\Component\TypeInfo\Type\IntersectionType;
23
use Symfony\Component\TypeInfo\Type\NullableType;
24
use Symfony\Component\TypeInfo\Type\ObjectType;
25
use Symfony\Component\TypeInfo\Type\UnionType;
26
use Symfony\Component\TypeInfo\TypeIdentifier;
27

28
/**
29
 * A helper about PropertyInfo Type conversion.
30
 *
31
 * @see https://github.com/mtarld/symfony/commits/backup/chore/deprecate-property-info-type/
32
 *
33
 * @author Mathias Arlaud <mathias.arlaud@gmail.com>
34
 *
35
 * @internal
36
 */
37
final class PropertyInfoToTypeInfoHelper
38
{
39
    /**
40
     * Converts a {@see LegacyType} to what is should have been in the "symfony/type-info" component.
41
     *
42
     * @param list<LegacyType>|null $legacyTypes
43
     */
44
    public static function convertLegacyTypesToType(?array $legacyTypes): ?Type
45
    {
46
        if (!$legacyTypes) {
58✔
47
            return null;
×
48
        }
49

50
        $types = [];
58✔
51
        $nullable = false;
58✔
52

53
        foreach (array_map(self::convertLegacyTypeToType(...), $legacyTypes) as $type) {
58✔
54
            if ($type->isNullable()) {
58✔
55
                $nullable = true;
×
56

57
                if ($type instanceof BuiltinType && TypeIdentifier::NULL === $type->getTypeIdentifier()) {
×
58
                    continue;
×
59
                }
60

61
                $type = self::unwrapNullableType($type);
×
62
            }
63

64
            if ($type instanceof UnionType) {
58✔
65
                $types = [$types, ...$type->getTypes()];
×
66

67
                continue;
×
68
            }
69

70
            $types[] = $type;
58✔
71
        }
72

73
        if ($nullable && [] === $types) {
58✔
74
            return Type::null();
×
75
        }
76

77
        $type = \count($types) > 1 ? Type::union(...$types) : $types[0];
58✔
78
        if ($nullable) {
58✔
79
            $type = Type::nullable($type);
×
80
        }
81

82
        return $type;
58✔
83
    }
84

85
    /**
86
     * @param list<LegacyType> $collectionKeyTypes
87
     * @param list<LegacyType> $collectionValueTypes
88
     */
89
    public static function createTypeFromLegacyValues(string $builtinType, bool $nullable, ?string $class, bool $collection, array $collectionKeyTypes, array $collectionValueTypes): Type
90
    {
91
        $variableTypes = [];
58✔
92

93
        if ($collectionKeyTypes) {
58✔
94
            $collectionKeyTypes = array_unique(array_map(self::convertLegacyTypeToType(...), $collectionKeyTypes));
×
95
            $variableTypes[] = \count($collectionKeyTypes) > 1 ? Type::union(...$collectionKeyTypes) : $collectionKeyTypes[0];
×
96
        }
97

98
        if ($collectionValueTypes) {
58✔
99
            if (!$collectionKeyTypes) {
×
100
                $variableTypes[] = \is_array($collectionKeyTypes) ? Type::mixed() : Type::union(Type::int(), Type::string()); // @phpstan-ignore-line
×
101
            }
102

103
            $collectionValueTypes = array_unique(array_map(self::convertLegacyTypeToType(...), $collectionValueTypes));
×
104
            $variableTypes[] = \count($collectionValueTypes) > 1 ? Type::union(...$collectionValueTypes) : $collectionValueTypes[0];
×
105
        }
106

107
        if ($collectionKeyTypes && !$collectionValueTypes) {
58✔
108
            $variableTypes[] = Type::mixed();
×
109
        }
110

111
        try {
112
            $type = null !== $class ? Type::object($class) : Type::builtin(TypeIdentifier::from($builtinType));
58✔
113
        } catch (\ValueError) {
×
114
            throw new InvalidArgumentException(\sprintf('"%s" is not a valid PHP type.', $builtinType));
×
115
        }
116

117
        if (\count($variableTypes)) {
58✔
118
            // hack to have generic without classname
119
            // this is required because some tests are using invalid data
NEW
120
            if (null === $class && 'object' === $builtinType) {
×
NEW
121
                $type = Type::object(\stdClass::class);
×
122
            }
UNCOV
123
            $type = Type::generic($type, ...$variableTypes);
×
124
        }
125

126
        if ($collection) {
58✔
127
            $type = Type::collection($type);
×
128
        }
129

130
        if ($nullable && !$type->isNullable()) {
58✔
131
            $type = Type::nullable($type);
×
132
        }
133

134
        return $type;
58✔
135
    }
136

137
    public static function unwrapNullableType(Type $type): Type
138
    {
139
        // BC layer for "symfony/type-info" < 7.2
140
        if (method_exists($type, 'asNonNullable')) {
×
141
            return (!$type instanceof UnionType) ? $type : $type->asNonNullable();
×
142
        }
143

144
        if (!$type instanceof NullableType) {
×
145
            return $type;
×
146
        }
147

148
        return $type->getWrappedType();
×
149
    }
150

151
    /**
152
     * Recursive method that converts {@see LegacyType} to its related {@see Type}.
153
     */
154
    private static function convertLegacyTypeToType(LegacyType $legacyType): Type
155
    {
156
        return self::createTypeFromLegacyValues(
58✔
157
            $legacyType->getBuiltinType(),
58✔
158
            $legacyType->isNullable(),
58✔
159
            $legacyType->getClassName(),
58✔
160
            $legacyType->isCollection(),
58✔
161
            $legacyType->getCollectionKeyTypes(),
58✔
162
            $legacyType->getCollectionValueTypes(),
58✔
163
        );
58✔
164
    }
165

166
    /**
167
     * Converts a {@see Type} to what is should have been in the "symfony/property-info" component.
168
     *
169
     * @return list<LegacyType>|null
170
     */
171
    public static function convertTypeToLegacyTypes(?Type $type): ?array
172
    {
173
        if (null === $type) {
514✔
174
            return null;
75✔
175
        }
176

177
        if (\in_array((string) $type, ['mixed', 'never'], true)) {
481✔
NEW
178
            return null;
×
179
        }
180

181
        if (\in_array((string) $type, ['null', 'void'], true)) {
481✔
NEW
182
            return [new LegacyType('null')];
×
183
        }
184

185
        $legacyType = self::convertTypeToLegacy($type);
481✔
186

187
        if (!\is_array($legacyType)) {
481✔
188
            $legacyType = [$legacyType];
481✔
189
        }
190

191
        return $legacyType;
481✔
192
    }
193

194
    /**
195
     * Recursive method that converts {@see Type} to its related {@see LegacyType} (or list of {@see @LegacyType}).
196
     *
197
     * @return LegacyType|list<LegacyType>
198
     */
199
    private static function convertTypeToLegacy(Type $type): LegacyType|array
200
    {
201
        $nullable = false;
481✔
202

203
        if ($type instanceof NullableType) {
481✔
204
            $nullable = true;
285✔
205
            $type = $type->getWrappedType();
285✔
206
        }
207

208
        if ($type instanceof UnionType) {
481✔
209
            $unionTypes = [];
16✔
210
            foreach ($type->getTypes() as $t) {
16✔
211
                if ($t instanceof IntersectionType) {
16✔
NEW
212
                    throw new \LogicException(\sprintf('DNF types are not supported by "%s".', LegacyType::class));
×
213
                }
214

215
                if ($nullable) {
16✔
216
                    $t = Type::nullable($t);
15✔
217
                }
218

219
                $unionTypes[] = $t;
16✔
220
            }
221

222
            /** @var list<LegacyType> $legacyTypes */
223
            $legacyTypes = array_map(self::convertTypeToLegacy(...), $unionTypes);
16✔
224

225
            if (1 === \count($legacyTypes)) {
16✔
NEW
226
                return $legacyTypes[0];
×
227
            }
228

229
            return $legacyTypes;
16✔
230
        }
231

232
        if ($type instanceof IntersectionType) {
481✔
233
            /** @var list<LegacyType> $legacyTypes */
NEW
234
            $legacyTypes = array_map(self::convertTypeToLegacy(...), $type->getTypes());
6✔
235

NEW
236
            if (1 === \count($legacyTypes)) {
6✔
NEW
237
                return $legacyTypes[0];
×
238
            }
239

NEW
240
            return $legacyTypes;
6✔
241
        }
242

243
        if ($type instanceof CollectionType) {
481✔
244
            $type = $type->getWrappedType();
164✔
245
            if ($nullable) {
164✔
246
                $type = Type::nullable($type);
47✔
247
            }
248

249
            return self::convertTypeToLegacy($type);
164✔
250
        }
251

252
        $typeIdentifier = TypeIdentifier::MIXED;
481✔
253
        $className = null;
481✔
254
        $collectionKeyType = $collectionValueType = null;
481✔
255

256
        if ($type instanceof GenericType) {
481✔
257
            $wrappedType = $type->getWrappedType();
135✔
258

259
            if ($wrappedType instanceof BuiltinType) {
135✔
260
                $typeIdentifier = $wrappedType->getTypeIdentifier();
51✔
261
            } elseif ($wrappedType instanceof ObjectType) {
120✔
262
                $typeIdentifier = TypeIdentifier::OBJECT;
120✔
263
                $className = $wrappedType->getClassName();
120✔
264
            }
265

266
            $variableTypes = $type->getVariableTypes();
135✔
267

268
            if (2 === \count($variableTypes)) {
135✔
269
                if ('int|string' !== (string) $variableTypes[0]) {
135✔
270
                    $collectionKeyType = self::convertTypeToLegacy($variableTypes[0]);
128✔
271
                }
272
                $collectionValueType = self::convertTypeToLegacy($variableTypes[1]);
135✔
NEW
273
            } elseif (1 === \count($variableTypes)) {
×
NEW
274
                $collectionValueType = self::convertTypeToLegacy($variableTypes[0]);
×
275
            }
276
        } elseif ($type instanceof ObjectType) {
481✔
277
            $typeIdentifier = TypeIdentifier::OBJECT;
260✔
278
            $className = $type->getClassName();
260✔
279
        } elseif ($type instanceof BuiltinType) {
474✔
280
            $typeIdentifier = $type->getTypeIdentifier();
474✔
281
        }
282

283
        if (TypeIdentifier::MIXED === $typeIdentifier) {
481✔
NEW
284
            return [
4✔
NEW
285
                new LegacyType(LegacyType::BUILTIN_TYPE_INT, true),
4✔
NEW
286
                new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT, true),
4✔
NEW
287
                new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true),
4✔
NEW
288
                new LegacyType(LegacyType::BUILTIN_TYPE_BOOL, true),
4✔
NEW
289
                new LegacyType(LegacyType::BUILTIN_TYPE_RESOURCE, true),
4✔
NEW
290
                new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true),
4✔
NEW
291
                new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, true),
4✔
NEW
292
                new LegacyType(LegacyType::BUILTIN_TYPE_NULL, true),
4✔
NEW
293
                new LegacyType(LegacyType::BUILTIN_TYPE_CALLABLE, true),
4✔
NEW
294
                new LegacyType(LegacyType::BUILTIN_TYPE_ITERABLE, true),
4✔
NEW
295
            ];
4✔
296
        }
297

298
        return new LegacyType(
481✔
299
            builtinType: $typeIdentifier->value,
481✔
300
            nullable: $nullable,
481✔
301
            class: $className,
481✔
302
            collection: $type instanceof GenericType,
481✔
303
            collectionKeyType: $collectionKeyType,
481✔
304
            collectionValueType: $collectionValueType,
481✔
305
        );
481✔
306
    }
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc