8000 convert legacy types to TypeInfo types if getType() is not implemented · symfony/symfony@98550a9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 98550a9

Browse files
committed
convert legacy types to TypeInfo types if getType() is not implemented
1 parent 6870e0f commit 98550a9

File tree

5 files changed

+306
-2
lines changed

5 files changed

+306
-2
lines changed

src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\PropertyInfo;
1313

1414
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\PropertyInfo\Util\LegacyTypeConverter;
1516
use Symfony\Component\TypeInfo\Type;
1617

1718
/**
@@ -61,7 +62,40 @@ public function getProperties(string $class, array $context = []): ?array
6162
*/
6263
public function getType(string $class, string $property, array $context = []): ?Type
6364
{
64-
return $this->extract('getType', [$class, $property, $context]);
65+
try {
66+
$serializedArguments = serialize([$class, $property, $context]);
67+
} catch (\Exception) {
68+
// If arguments are not serializable, skip the cache
69+
if (method_exists($this->propertyInfoExtractor, 'getType')) {
70+
return $this->propertyInfoExtractor->getType($class, $property, $context);
71+
}
72+
73+
return LegacyTypeConverter::toTypeInfoType($this->propertyInfoExtractor->getTypes($class, $property, $context));
74+
}
75+
76+
// Calling rawurlencode escapes special characters not allowed in PSR-6's keys
77+
$key = rawurlencode('getType.'.$serializedArguments);
78+
79+
if (\array_key_exists($key, $this->arrayCache)) {
80+
return $this->arrayCache[$key];
81+
}
82+
83+
$item = $this->cacheItemPool->getItem($key);
84+
85+
if ($item->isHit()) {
86+
return $this->arrayCache[$key] = $item->get();
87+
}
88+
89+
if (method_exists($this->propertyInfoExtractor, 'getType')) {
90+
$value = $this->propertyInfoExtractor->getType($class, $property, $context);
91+
} else {
92+
$value = LegacyTypeConverter::toTypeInfoType($this->propertyInfoExtractor->getTypes($class, $property, $context));
93+
}
94+
95+
$item->set($value);
96+
$this->cacheItemPool->save($item);
97+
98+
return $this->arrayCache[$key] = $value;
6599
}
66100

67101
public function getTypes(string $class, string $property, array $context = []): ?array

src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\PropertyInfo;
1313

14+
use Symfony\Component\PropertyInfo\Util\LegacyTypeConverter;
1415
use Symfony\Component\TypeInfo\Type;
1516

1617
/**
@@ -58,7 +59,23 @@ public function getLongDescription(string $class, string $property, array $conte
5859
*/
5960
public function getType(string $class, string $property, array $context = []): ?Type
6061
{
61-
return $this->extract($this->typeExtractors, 'getType', [$class, $property, $context]);
62+
foreach ($this->typeExtractors as $extractor) {
63+
if (!method_exists($extractor, 'getType')) {
64+
$legacyTypes = $extractor->getTypes($class, $property, $context);
65+
66+
if (null !== $legacyTypes) {
67+
return LegacyTypeConverter::toTypeInfoType($legacyTypes);
68+
}
69+
70+
continue;
71+
}
72+
73+
if (null !== $value = $extractor->getType($class, $property, $context)) {
74+
return $value;
75+
}
76+
}
77+
78+
return null;
6279
}
6380

6481
public function getTypes(string $class, string $property, array $context = []): ?array

src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
namespace Symfony\Component\PropertyInfo\Tests;
1313

1414
use Symfony\Component\Cache\Adapter\ArrayAdapter;
15+
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1516
use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor;
17+
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
18+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
20+
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
21+
use Symfony\Component\TypeInfo\Type;
1622

1723
/**
1824
* @author Kévin Dunglas <dunglas@gmail.com>
@@ -76,4 +82,90 @@ public function testIsInitializable()
7682
parent::testIsInitializable();
7783
parent::testIsInitializable();
7884
}
85+
86+
/**
87+
* @group legacy
88+
*
89+
* @dataProvider provideNestedExtractorWithoutGetTypeImplementationData
90+
*/
91+
public function testNestedExtractorWithoutGetTypeImplementation(string $property, ?Type $expectedType)
92+
{
93+
$propertyInfoCacheExtractor = new PropertyInfoCacheExtractor(new class() implements PropertyInfoExtractorInterface {
94+
private PropertyTypeExtractorInterface $propertyTypeExtractor;
95+
96+
public function __construct()
97+
{
98+
$this->propertyTypeExtractor = new PhpDocExtractor();
99+
}
100+
101+
public function getTypes(string $class, string $property, array $context = []): ?array
102+
{
103+
return $this->propertyTypeExtractor->getTypes($class, $property, $context);
104+
}
105+
106+
public function isReadable(string $class, string $property, array $context = []): ?bool
107+
{
108+
return null;
109+
}
110+
111+
public function isWritable(string $class, string $property, array $context = []): ?bool
112+
{
113+
return null;
114+
}
115+
116+
public function getShortDescription(string $class, string $property, array $context = []): ?string
117+
{
118+
return null;
119+
}
120+
121+
public function getLongDescription(string $class, string $property, array $context = []): ?string
122+
{
123+
return null;
124+
}
125+
126+
public function getProperties(string $class, array $context = []): ?array
127+
{
128+
return null;
129+
}
130+
}, new ArrayAdapter());
131+
132+
if (null === $expectedType) {
133+
$this->assertNull($propertyInfoCacheExtractor->getType(Dummy::class, $property));
134+
} else {
135+
$this->assertEquals($expectedType, $propertyInfoCacheExtractor->getType(Dummy::class, $property));
136+
}
137+
}
138+
139+
public function provideNestedExtractorWithoutGetTypeImplementationData()
140+
{
141+
yield ['bar', Type::string()];
142+
yield ['baz', Type::int()];
143+
yield ['bal', Type::object(\DateTimeImmutable::class)];
144+
yield ['parent', Type::object(ParentDummy::class)];
145+
yield ['collection', Type::array(Type::object(\DateTimeImmutable::class), Type::int())];
146+
yield ['nestedCollection', Type::array(Type::array(Type::string(), Type::int()), Type::int())];
147+
yield ['mixedCollection', Type::array()];
148+
yiel F438 d ['B', Type::object(ParentDummy::class)];
149+
yield ['Id', Type::int()];
150+
yield ['Guid', Type::string()];
151+
yield ['g', Type::nullable(Type::array())];
152+
yield ['h', Type::nullable(Type::string())];
153+
yield ['i', Type::nullable(Type::union(Type::string(), Type::int()))];
154+
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
155+
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::array(Type::int(), Type::int()))];
156+
yield ['nonNullableCollectionOfNullableElements', Type::array(Type::nullable(Type::int()), Type::int())];
157+
yield ['nullableCollectionOfMultipleNonNullableElementTypes', Type::nullable(Type::array(Type::union(Type::int(), Type::string()), Type::int()))];
158+
yield ['xTotals', Type::array()];
159+
yield ['YT', Type::string()];
160+
yield ['emptyVar', null];
161+
yield ['iteratorCollection', Type::collection(Type::object(\Iterator::class), Type::string(), Type::union(Type::string(), Type::int()))];
162+
yield ['iteratorCollectionWithKey', Type::collection(Type::object(\Iterator::class), Type::string(), Type::int())];
163+
yield ['nestedIterators', Type::collection(Type::object(\Iterator::class), Type::collection(Type::object(\Iterator::class), Type::string(), Type::int()), Type::int())];
164+
yield ['arrayWithKeys', Type::array(Type::string(), Type::string())];
165+
yield ['arrayWithKeysAndComplexValue', Type::array(Type::nullable(Type::array(Type::nullable(Type::string()), Type::int())), Type::string())];
166+
yield ['arrayOfMixed', Type::array(Type::mixed(), Type::string())];
167+
yield ['noDocBlock', null];
168+
yield ['listOfStrings', Type::array(Type::string(), Type::int())];
169+
yield ['parentAnnotation', Type::object(ParentDummy::class)];
170+
}
79171
}

src/Symfony/Component/PropertyInfo/Tests/PropertyInfoExtractorTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,76 @@
1111

1212
namespace Symfony\Component\PropertyInfo\Tests;
1313

14+
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
15+
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
16+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
17+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
18+
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
19+
use Symfony\Component\TypeInfo\Type;
20+
1421
/**
1522
* @author Kévin Dunglas <dunglas@gmail.com>
1623
*/
1724
class PropertyInfoExtractorTest extends AbstractPropertyInfoExtractorTest
1825
{
26+
/**
27+
* @group legacy
28+
*
29+
* @dataProvider provideNestedExtractorWithoutGetTypeImplementationData
30+
*/
31+
public function testNestedExtractorWithoutGetTypeImplementation(string $property, ?Type $expectedType)
32+
{
33+
$propertyInfoExtractor = new PropertyInfoExtractor([], [new class() implements PropertyTypeExtractorInterface {
34+
private PropertyTypeExtractorInterface $propertyTypeExtractor;
35+
36+
public function __construct()
37+
{
38+
$this->propertyTypeExtractor = new PhpDocExtractor();
39+
}
40+
41+
public function getTypes(string $class, string $property, array $context = []): ?array
42+
{
43+
return $this->propertyTypeExtractor->getTypes($class, $property, $context);
44+
}
45+
}]);
46+
47+
if (null === $expectedType) {
48+
$this->assertNull($propertyInfoExtractor->getType(Dummy::class, $property));
49+
} else {
50+
$this->assertEquals($expectedType, $propertyInfoExtractor->getType(Dummy::class, $property));
51+
}
52+
}
53+
54+
public function provideNestedExtractorWithoutGetTypeImplementationData()
55+
{
56+
yield ['bar', Type::string()];
57+
yield ['baz', Type::int()];
58+
yield ['bal', Type::object(\DateTimeImmutable::class)];
59+
yield ['parent', Type::object(ParentDummy::class)];
60+
yield ['collection', Type::array(Type::object(\DateTimeImmutable::class), Type::int())];
61+
yield ['nestedCollection', Type::array(Type::array(Type::string(), Type::int()), Type::int())];
62+
yield ['mixedCollection', Type::array()];
63+
yield ['B', Type::object(ParentDummy::class)];
64+
yield ['Id', Type::int()];
65+
yield ['Guid', Type::string()];
66+
yield ['g', Type::nullable(Type::array())];
67+
yield ['h', Type::nullable(Type::string())];
68+
yield ['i', Type::nullable(Type::union(Type::string(), Type::int()))];
69+
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
70+
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::array(Type::int(), Type::int()))];
71+
yield ['nonNullableCollectionOfNullableElements', Type::array(Type::nullable(Type::int()), Type::int())];
72+
yield ['nullableCollectionOfMultipleNonNullableElementTypes', Type::nullable(Type::array(Type::union(Type::int(), Type::string()), Type::int()))];
73+
yield ['xTotals', Type::array()];
74+
yield ['YT', Type::string()];
75+
yield ['emptyVar', null];
76+
yield ['iteratorCollection', Type::collection(Type::object(\Iterator::class), Type::string(), Type::union(Type::string(), Type::int()))];
77+
yield ['iteratorCollectionWithKey', Type::collection(Type::object(\Iterator::class), Type::string(), Type::int())];
78+
yield ['nestedIterators', Type::collection(Type::object(\Iterator::class), Type::collection(Type::object(\Iterator::class), Type::string(), Type::int()), Type::int())];
79+
yield ['arrayWithKeys', Type::array(Type::string(), Type::string())];
80+
yield ['arrayWithKeysAndComplexValue', Type::array(Type::nullable(Type::array(Type::nullable(Type::string()), Type::int())), Type::string())];
81+
yield ['arrayOfMixed', Type::array(Type::mixed(), Type::string())];
82+
yield ['noDocBlock', null];
83+
yield ['listOfStrings', Type::array(Type::string(), Type::int())];
84+
yield ['parentAnnotation', Type::object(ParentDummy::class)];
85+
}
1986
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\PropertyInfo\Util;
13+
14+
use Symfony\Component\PropertyInfo\Type as LegacyType;
15+
use Symfony\Component\TypeInfo\Type;
16+
17+
/**
18+
* @internal
19+
*/
20+
class LegacyTypeConverter
21+
{
22+
/**
23+
* @param LegacyType[]|null $legacyTypes
24+
*/
25+
public static function toTypeInfoType(?array $legacyTypes): ?Type
26+
{
27+
if (null === $legacyTypes || [] === $legacyTypes) {
28+
return null;
29+
}
30+
31+
$nullable = false;
32+
$types = [];
33+
34+
foreach ($legacyTypes as $legacyType) {
35+
switch ($legacyType->getBuiltinType()) {
36+
case LegacyType::BUILTIN_TYPE_ARRAY:
37+
$typeInfoType = Type::array(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
38+
break;
39+
case LegacyType::BUILTIN_TYPE_BOOL:
40+
$typeInfoType = Type::bool();
41+
break;
42+
case LegacyType::BUILTIN_TYPE_CALLABLE:
43+
$typeInfoType = Type::callable();
44+
break;
45+
case LegacyType::BUILTIN_TYPE_FALSE:
46+
$typeInfoType = Type::false();
47+
break;
48+
case LegacyType::BUILTIN_TYPE_FLOAT:
49+
$typeInfoType = Type::float();
50+
break;
51+
case LegacyType::BUILTIN_TYPE_INT:
52+
$typeInfoType = Type::int();
53+
break;
54+
case LegacyType::BUILTIN_TYPE_ITERABLE:
55+
$typeInfoType = Type::iterable(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
56+
break;
57+
case LegacyType::BUILTIN_TYPE_OBJECT:
58+
if ($legacyType->isCollection()) {
59+
$typeInfoType = Type::collection(Type::object($legacyType->getClassName()), self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
60+
} else {
61+
$typeInfoType = Type::object($legacyType->getClassName());
62+
}
63+
64+
break;
65+
case LegacyType::BUILTIN_TYPE_RESOURCE:
66+
$typeInfoType = Type::resource();
67+
break;
68+
case LegacyType::BUILTIN_TYPE_STRING:
69+
$typeInfoType = Type::string();
70+
break;
71+
case LegacyType::BUILTIN_TYPE_TRUE:
72+
$typeInfoType = Type::true();
73+
break;
74+
default:
75+
$typeInfoType = null;
76+
break;
77+
}
78+
79+
if (LegacyType::BUILTIN_TYPE_NULL === $legacyType->getBuiltinType() || $legacyType->isNullable()) {
80+
$nullable = true;
81+
}
82+
83+
if (null !== $typeInfoType) {
84+
$types[] = $typeInfoType;
85+
}
86+
}
87+
88+
if (1 === \count($types)) {
89+
return $nullable ? Type::nullable($types[0]) : $types[0];
90+
}
91+
92+
return $nullable ? Type::nullable(Type::union(...$types)) : Type::union(...$types);
93+
}
94+
}

0 commit comments

Comments
 (0)
0