Skip to content

Commit c8c4b55

Browse files
committed
convert legacy types to TypeInfo types if getType() is not implemented
1 parent 7abc106 commit c8c4b55

File tree

5 files changed

+277
-2
lines changed

5 files changed

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

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,74 @@
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+
* @dataProvider provideNestedExtractourWithoutGetTypeImplementationData
28+
*/
29+
public function testNestedExtractorWithoutGetTypeImplementation(string $property, ?Type $expectedType)
30+
{
31+
$propertyInfoExtractor = new PropertyInfoExtractor([], [new class() implements PropertyTypeExtractorInterface {
32+
private PropertyTypeExtractorInterface $propertyTypeExtractor;
33+
34+
public function __construct()
35+
{
36+
$this->propertyTypeExtractor = new PhpDocExtractor();
37+
}
38+
39+
public function getTypes(string $class, string $property, array $context = []): ?array
40+
{
41+
return $this->propertyTypeExtractor->getTypes($class, $property, $context);
42+
}
43+
}]);
44+
45+
if (null === $expectedType) {
46+
$this->assertNull($propertyInfoExtractor->getType(Dummy::class, $property));
47+
} else {
48+
$this->assertEquals($expectedType, $propertyInfoExtractor->getType(Dummy::class, $property));
49+
}
50+
}
51+
52+
public function provideNestedExtractourWithoutGetTypeImplementationData()
53+
{
54+
yield ['bar', Type::string()];
55+
yield ['baz', Type::int()];
56+
yield ['bal', Type::object(\DateTimeImmutable::class)];
57+
yield ['parent', Type::object(ParentDummy::class)];
58+
yield ['collection', Type::array(Type::object(\DateTimeImmutable::class), Type::int())];
59+
yield ['nestedCollection', Type::array(Type::array(Type::string(), Type::int()), Type::int())];
60+
yield ['mixedCollection', Type::array()];
61+
yield ['B', Type::object(ParentDummy::class)];
62+
yield ['Id', Type::int()];
63+
yield ['Guid', Type::string()];
64+
yield ['g', Type::nullable(Type::array())];
65+
yield ['h', Type::nullable(Type::string())];
66+
yield ['i', Type::nullable(Type::union(Type::string(), Type::int()))];
67+
yield ['j', Type::nullable(Type::object(\DateTimeImmutable::class))];
68+
yield ['nullableCollectionOfNonNullableElements', Type::nullable(Type::array(Type::int(), Type::int()))];
69+
yield ['nonNullableCollectionOfNullableElements', Type::array(Type::nullable(Type::int()), Type::int())];
70+
yield ['nullableCollectionOfMultipleNonNullableElementTypes', Type::nullable(Type::array(Type::union(Type::int(), Type::string()), Type::int()))];
71+
yield ['xTotals', Type::array()];
72+
yield ['YT', Type::string()];
73+
yield ['emptyVar', null];
74+
yield ['iteratorCollection', Type::collection(Type::object(\Iterator::class), Type::string(), Type::union(Type::string(), Type::int()))];
75+
yield ['iteratorCollectionWithKey', Type::collection(Type::object(\Iterator::class), Type::string(), Type::int())];
76+
yield ['nestedIterators', Type::collection(Type::object(\Iterator::class), Type::collection(Type::object(\Iterator::class), Type::string(), Type::int()), Type::int())];
77+
yield ['arrayWithKeys', Type::array(Type::string(), Type::string())];
78+
yield ['arrayWithKeysAndComplexValue', Type::array(Type::nullable(Type::array(Type::nullable(Type::string()), Type::int())), Type::string())];
79+
yield ['arrayOfMixed', Type::array(Type::mixed(), Type::string())];
80+
yield ['noDocBlock', null];
81+
yield ['listOfStrings', Type::array(Type::string(), Type::int())];
82+
yield ['parentAnnotation', Type::object(ParentDummy::class)];
83+
}
1984
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
if ($legacyType->isCollection() && !\in_array($legacyType->getBuiltinType(), [LegacyType::BUILTIN_TYPE_ARRAY, LegacyType::BUILTIN_TYPE_ITERABLE], true)) {
36+
$typeInfoType = Type::collection(Type::object(\Iterator::class), self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
37+
} else {
38+
$typeInfoType = match ($legacyType->getBuiltinType()) {
39+
LegacyType::BUILTIN_TYPE_ARRAY => Type::array(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes())),
40+
LegacyType::BUILTIN_TYPE_BOOL => Type::bool(),
41+
LegacyType::BUILTIN_TYPE_CALLABLE => Type::callable(),
42+
LegacyType::BUILTIN_TYPE_FALSE => Type::false(),
43+
LegacyType::BUILTIN_TYPE_FLOAT => Type::float(),
44+
LegacyType::BUILTIN_TYPE_INT => Type::int(),
45+
LegacyType::BUILTIN_TYPE_ITERABLE => Type::iterable(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes())),
46+
LegacyType::BUILTIN_TYPE_OBJECT => Type::object($legacyType->getClassName()),
47+
LegacyType::BUILTIN_TYPE_RESOURCE => Type::resource(),
48+
LegacyType::BUILTIN_TYPE_STRING => Type::string(),
49+
LegacyType::BUILTIN_TYPE_TRUE => Type::true(),
50+
default => null,
51+
};
52+
}
53+
54+
if (LegacyType::BUILTIN_TYPE_NULL === $legacyType->getBuiltinType() || $legacyType->isNullable()) {
55+
$nullable = true;
56+
}
57+
58+
if (null !== $typeInfoType) {
59+
$types[] = $typeInfoType;
60+
}
61+
}
62+
63+
if (1 === \count($types)) {
64+
return $nullable ? Type::nullable($types[0]) : $types[0];
65+
}
66+
67+
return $nullable ? Type::nullable(Type::union(...$types)) : Type::union(...$types);
68+
}
69+
}

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