Skip to content

Commit 6ccc82d

Browse files
committed
feature #52510 [TypeInfo] Introduce component (mtarld)
This PR was merged into the 7.1 branch. Discussion ---------- [TypeInfo] Introduce component | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | | License | MIT | Doc PR | TODO Introducing the brand new `TypeInfo` component This work has been done with `@Korbeil` ## State of the art ### Scope of the current `Symfony\Component\PropertyInfo\Type` class Nowadays, when we need to work with types within Symfony, we have to use the `Type` class of the `PropertyInfo` component. But what if we want to represent the type of a function's return type? We still can use that `Type` class, but it won't make much sense as the `Symfony\Component\PropertyInfo\Type` is closely related to a property (as the namespace suggests). Plus, when we need to extract types, we must use the `PropertyTypeExtractorInterface` service: ```php readonly class Person { public function __construct( public string $firstName, ) { } } // will return an array with a single Type object representing a string $types = $this->propertyTypeExtractor->getTypes(Person::class, 'firstName'); ``` Therefore, type retrieval in Symfony is limited to properties only. ### `Symfony\Component\PropertyInfo\Type`'s conceptual limitations On top of that, there is a clear limitation of the current `Type` class where unions, intersections or even generics can't be properly described. The actual workaround is that the `PropertyTypeExtractorInterface` is returning an array of `Type`, which can be interpreted as a union type. ## The `TypeInfo` component All these reasons bring us to create the `TypeInfo` component. The aim here is to address these issues and: - Have a powerful `Type` definition that can handle union, intersections, and generics (and could be even more extended) - Being able to get types from anything, such as properties, method arguments, return types, and raw strings (and can also be extended). ### `Type` classes To ensure a powerful `Type` definition, we defined multiple classes: ![Type classes](https://i.imgur.com/Exs5gcY.png) The base `Type` class is an abstract one, so you'll always need to use one of the classes that inherit it. Other types of classes are kinda self-explanatory. ### Type resolving In the `TypeInfo` component, we added a `TypeResolverInterface`, and several implementations which allow developers to get a `Type` from many things: - `ReflectionParameterTypeResolver` to resolve a function/method parameter type thanks to reflection - `ReflectionPropertyTypeResolver` to resolve a property type thanks to reflection - `ReflectionReturnTypeResolver` to resolve a function/method return type thanks to reflection - `ReflectionTypeResolver` to resolve a `ReflectionNamedType` - `StringTypeResolver` to resolve a type string representation. This can greatly work in combination with PHP documentation. > That resolver will only be available if the `phpstan/phpdoc-parser` package is installed. - `ChainTypeResolver` to iterate over resolvers and to return a type as soon as a nested resolver succeeds in resolving. ### Type Creation We also improved a lot the DX for the type creation with factories: ```php <?php use Symfony\Component\TypeInfo\Type; Type::int(); Type::nullable(Type::string()); Type::generic(Type::object(Collection::class), Type::int()); Type::list(Type::bool()); Type::intersection(Type::object(\Stringable::class), Type::object(\Iterator::class)); // Many others are available and can be // found in Symfony\Component\TypeInfo\TypeFactoryTrait ``` ### Upgrade path This PR only introduces the `TypeInfo` component, but another one (which is already ready) will deprecate the `Symfony\Component\PropertyInfo\Type` in favor of `Symfony\Component\TypeInfo\Type`. Commits ------- 6de7d7df0d [TypeInfo] Introduce component
2 parents ffcf3fe + e95e5b8 commit 6ccc82d

File tree

17 files changed

+223
-0
lines changed

17 files changed

+223
-0
lines changed

DependencyInjection/Configuration.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use Symfony\Component\Serializer\Encoder\JsonDecode;
4545
use Symfony\Component\Serializer\Serializer;
4646
use Symfony\Component\Translation\Translator;
47+
use Symfony\Component\TypeInfo\Type;
4748
use Symfony\Component\Uid\Factory\UuidFactory;
4849
use Symfony\Component\Validator\Validation;
4950
use Symfony\Component\Webhook\Controller\WebhookController;
@@ -164,6 +165,7 @@ public function getConfigTreeBuilder(): TreeBuilder
164165
$this->addAnnotationsSection($rootNode);
165166
$this->addSerializerSection($rootNode, $enableIfStandalone);
166167
$this->addPropertyAccessSection($rootNode, $willBeAvailable);
168+
$this->addTypeInfoSection($rootNode, $enableIfStandalone);
167169
$this->addPropertyInfoSection($rootNode, $enableIfStandalone);
168170
$this->addCacheSection($rootNode, $willBeAvailable);
169171
$this->addPhpErrorsSection($rootNode);
@@ -1161,6 +1163,18 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable
11611163
;
11621164
}
11631165

1166+
private function addTypeInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void
1167+
{
1168+
$rootNode
1169+
->children()
1170+
->arrayNode('type_info')
1171+
->info('Type info configuration')
1172+
->{$enableIfStandalone('symfony/type-info', Type::class)}()
1173+
->end()
1174+
->end()
1175+
;
1176+
}
1177+
11641178
private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void
11651179
{
11661180
$rootNode

DependencyInjection/FrameworkExtension.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
use Symfony\Component\Console\Debug\CliRequest;
5454
use Symfony\Component\Console\Messenger\RunCommandMessageHandler;
5555
use Symfony\Component\DependencyInjection\Alias;
56+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
5657
use Symfony\Component\DependencyInjection\ChildDefinition;
5758
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
5859
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -168,6 +169,8 @@
168169
use Symfony\Component\Translation\LocaleSwitcher;
169170
use Symfony\Component\Translation\PseudoLocalizationTranslator;
170171
use Symfony\Component\Translation\Translator;
172+
use Symfony\Component\TypeInfo\Type;
173+
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
171174
use Symfony\Component\Uid\Factory\UuidFactory;
172175
use Symfony\Component\Uid\UuidV4;
173176
use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider;
@@ -390,6 +393,10 @@ public function load(array $configs, ContainerBuilder $container): void
390393
$container->removeDefinition('console.command.serializer_debug');
391394
}
392395

396+
if ($this->readConfigEnabled('type_info', $container, $config['type_info'])) {
397+
$this->registerTypeInfoConfiguration($container, $loader);
398+
}
399+
393400
if ($propertyInfoEnabled) {
394401
$this->registerPropertyInfoConfiguration($container, $loader);
395402
}
@@ -1956,6 +1963,25 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container,
19561963
}
19571964
}
19581965

1966+
private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void
1967+
{
1968+
if (!class_exists(Type::class)) {
1969+
throw new LogicException('TypeInfo support cannot be enabled as the TypeInfo component is not installed. Try running "composer require symfony/type-info".');
1970+
}
1971+
1972+
$loader->load('type_info.php');
1973+
1974+
if (ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/type-info'])) {
1975+
$container->register('type_info.resolver.string', StringTypeResolver::class);
1976+
1977+
/** @var ServiceLocatorArgument $resolversLocator */
1978+
$resolversLocator = $container->getDefinition('type_info.resolver')->getArgument(0);
1979+
$resolversLocator->setValues($resolversLocator->getValues() + [
1980+
'string' => new Reference('type_info.resolver.string'),
1981+
]);
1982+
}
1983+
}
1984+
19591985
private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
19601986
{
19611987
$loader->load('lock.php');

Resources/config/schema/symfony-1.0.xsd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<xsd:element name="property-access" type="property_access" minOccurs="0" maxOccurs="1" />
2828
<xsd:element name="scheduler" type="scheduler" minOccurs="0" maxOccurs="1" />
2929
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
30+
<xsd:element name="type-info" type="type_info" minOccurs="0" maxOccurs="1" />
3031
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
3132
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
3233
<xsd:element name="workflow" type="workflow" minOccurs="0" maxOccurs="unbounded" />
@@ -327,6 +328,10 @@
327328
<xsd:attribute name="max-depth-handler" type="xsd:string" />
328329
</xsd:complexType>
329330

331+
<xsd:complexType name="type_info">
332+
<xsd:attribute name="enabled" type="xsd:boolean" />
333+
</xsd:complexType>
334+
330335
<xsd:complexType name="property_info">
331336
<xsd:attribute name="enabled" type="xsd:boolean" />
332337
</xsd:complexType>

Resources/config/type_info.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
15+
use Symfony\Component\TypeInfo\TypeResolver\ReflectionParameterTypeResolver;
16+
use Symfony\Component\TypeInfo\TypeResolver\ReflectionPropertyTypeResolver;
17+
use Symfony\Component\TypeInfo\TypeResolver\ReflectionReturnTypeResolver;
18+
use Symfony\Component\TypeInfo\TypeResolver\ReflectionTypeResolver;
19+
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
20+
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
21+
22+
return static function (ContainerConfigurator $container) {
23+
$container->services()
24+
// type context
25+
->set('type_info.type_context_factory', TypeContextFactory::class)
26+
->args([service('type_info.resolver.string')->nullOnInvalid()])
27+
28+
// type resolvers
29+
->set('type_info.resolver', TypeResolver::class)
30+
->args([service_locator([
31+
\ReflectionType::class => service('type_info.resolver.reflection_type'),
32+
\ReflectionParameter::class => service('type_info.resolver.reflection_parameter'),
33+
\ReflectionProperty::class => service('type_info.resolver.reflection_property'),
34+
\ReflectionFunctionAbstract::class => service('type_info.resolver.reflection_return'),
35+
])])
36+
->alias(TypeResolverInterface::class, 'type_info.resolver')
37+
38+
->set('type_info.resolver.reflection_type', ReflectionTypeResolver::class)
39+
->args([service('type_info.type_context_factory')])
40+
41+
->set('type_info.resolver.reflection_parameter', ReflectionParameterTypeResolver::class)
42+
->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')])
43+
44+
->set('type_info.resolver.reflection_property', ReflectionPropertyTypeResolver::class)
45+
->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')])
46+
47+
->set('type_info.resolver.reflection_return', ReflectionReturnTypeResolver::class)
48+
->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')])
49+
;
50+
};

Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
2929
use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
3030
use Symfony\Component\Serializer\Encoder\JsonDecode;
31+
use Symfony\Component\TypeInfo\Type;
3132
use Symfony\Component\Uid\Factory\UuidFactory;
3233

3334
class ConfigurationTest extends TestCase
@@ -664,6 +665,9 @@ protected static function getBundleDefaultConfig()
664665
'throw_exception_on_invalid_index' => false,
665666
'throw_exception_on_invalid_property_path' => true,
666667
],
668+
'type_info' => [
669+
'enabled' => !class_exists(FullStack::class) && class_exists(Type::class),
670+
],
667671
'property_info' => [
668672
'enabled' => !class_exists(FullStack::class),
669673
],

Tests/DependencyInjection/Fixtures/php/full.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
'default_context' => ['enable_max_depth' => true],
7171
],
7272
'property_info' => true,
73+
'type_info' => true,
7374
'ide' => 'file%%link%%format',
7475
'request' => [
7576
'formats' => [
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'annotations' => false,
5+
'http_method_override' => false,
6+
'handle_all_throwables' => true,
7+
'php_errors' => ['log' => true],
8+
'type_info' => [
9+
'enabled' => true,
10+
],
11+
]);

Tests/DependencyInjection/Fixtures/xml/full.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@
4040
</framework:default-context>
4141
</framework:serializer>
4242
<framework:property-info />
43+
<framework:type-info />
4344
</framework:config>
4445
</container>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config http-method-override="false" handle-all-throwables="true">
9+
<framework:annotations enabled="false" />
10+
<framework:php-errors log="true" />
11+
<framework:type-info enabled="true" />
12+
</framework:config>
13+
</container>

Tests/DependencyInjection/Fixtures/yml/full.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ framework:
5959
max_depth_handler: my.max.depth.handler
6060
default_context:
6161
enable_max_depth: true
62+
type_info: ~
6263
property_info: ~
6364
ide: file%%link%%format
6465
request:

0 commit comments

Comments
 (0)