Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* Allow using their name without added suffix when using `#[Target]` for custom services
* Deprecate `Symfony\Bundle\FrameworkBundle\Console\Application::add()` in favor of `Symfony\Bundle\FrameworkBundle\Console\Application::addCommand()`
* Add `assertEmailAddressNotContains()` to the `MailerAssertionsTrait`
* Add `framework.type_info.aliases` option

7.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,17 @@ private function addTypeInfoSection(ArrayNodeDefinition $rootNode, callable $ena
->arrayNode('type_info')
->info('Type info configuration')
->{$enableIfStandalone('symfony/type-info', Type::class)}()
->addDefaultsIfNotSet()
->fixXmlConfig('alias', 'aliases')
->children()
->arrayNode('aliases')
->info('Additional type aliases to be used during type context creation.')
->defaultValue([])
->normalizeKeys(false)
->useAttributeAsKey('name')
->scalarPrototype()->end()
->end()
->end()
->end()
->end()
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ public function load(array $configs, ContainerBuilder $container): void
}

if ($typeInfoEnabled = $this->readConfigEnabled('type_info', $container, $config['type_info'])) {
$this->registerTypeInfoConfiguration($container, $loader);
$this->registerTypeInfoConfiguration($config['type_info'], $container, $loader);
}

if ($propertyInfoEnabled) {
Expand Down Expand Up @@ -2170,7 +2170,7 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild
}
}

private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void
private function registerTypeInfoConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
{
if (!class_exists(Type::class)) {
throw new LogicException('TypeInfo support cannot be enabled as the TypeInfo component is not installed. Try running "composer require symfony/type-info".');
Expand All @@ -2179,7 +2179,8 @@ private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpF
$loader->load('type_info.php');

if (ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/type-info'])) {
$container->register('type_info.resolver.string', StringTypeResolver::class);
$container->register('type_info.resolver.string', StringTypeResolver::class)
->setArguments([null, null, $config['aliases']]);

$container->register('type_info.resolver.reflection_parameter.phpdoc_aware', PhpDocAwareReflectionTypeResolver::class)
->setArguments([new Reference('type_info.resolver.reflection_parameter'), new Reference('type_info.resolver.string'), new Reference('type_info.type_context_factory')]);
Expand All @@ -2196,6 +2197,8 @@ private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpF
\ReflectionProperty::class => new Reference('type_info.resolver.reflection_property.phpdoc_aware'),
\ReflectionFunctionAbstract::class => new Reference('type_info.resolver.reflection_return.phpdoc_aware'),
] + $resolversLocator->getValues());

$container->getDefinition('type_info.type_context_factory')->replaceArgument(1, $config['aliases']);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,18 @@
</xsd:complexType>

<xsd:complexType name="type_info">
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:sequence>
<xsd:element name="alias" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="enabled" type="xsd:boolean"/>
</xsd:complexType>

<xsd:complexType name="named_serializer_options">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
$container->services()
// type context
->set('type_info.type_context_factory', TypeContextFactory::class)
->args([service('type_info.resolver.string')->nullOnInvalid()])
->args([
service('type_info.resolver.string')->nullOnInvalid(),
[],
])

// type resolvers
->set('type_info.resolver', TypeResolver::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ protected static function getBundleDefaultConfig()
],
'type_info' => [
'enabled' => !class_exists(FullStack::class) && class_exists(Type::class),
'aliases' => [],
],
'property_info' => [
'enabled' => !class_exists(FullStack::class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
'php_errors' => ['log' => true],
'type_info' => [
'enabled' => true,
'aliases' => [
'CustomAlias' => 'int',
],
],
]);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<framework:config http-method-override="false" handle-all-throwables="true">
<framework:annotations enabled="false" />
<framework:php-errors log="true" />
<framework:type-info enabled="true" />
<framework:type-info enabled="true">
<framework:alias name="CustomAlias">int</framework:alias>
</framework:type-info>
</framework:config>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ framework:
log: true
type_info:
enabled: true
aliases:
CustomAlias: int
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use PHPStan\PhpDocParser\Parser\PhpDocParser;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\TypeInfo\Dummy;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\TypeInfo\Type;

class TypeInfoTest extends AbstractWebTestCase
Expand All @@ -28,5 +29,10 @@ public function testComponent()
}

$this->assertEquals(Type::int(), static::getContainer()->get('type_info.resolver')->resolve('int'));

if (Kernel::VERSION_ID >= 70400) {
$this->assertEquals(Type::int(), static::getContainer()->get('type_info.resolver')->resolve(new \ReflectionProperty(Dummy::class, 'customAlias')));
$this->assertEquals(Type::int(), static::getContainer()->get('type_info.resolver')->resolve('CustomAlias'));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@
class Dummy
{
public string $name;

/**
* @var CustomAlias
*/
public mixed $customAlias;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ imports:

framework:
http_method_override: false
type_info: true
type_info:
aliases:
CustomAlias: int

services:
type_info.resolver.alias:
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/TypeInfo/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

7.4
---

* Add extra type alias support in `TypeContext` and `StringTypeResolver`

7.3
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,31 @@ public function testCollectTypeAliases()
], $this->typeContextFactory->createFromReflection(new \ReflectionClass(DummyWithImportedOnlyTypeAliases::class))->typeAliases);
}

public function testCollectTypeAliasesWithExtraTypeAliases()
{
$typeContextFactory = new TypeContextFactory(new StringTypeResolver(), [
'Custom' => Type::int(),
'CustomArray' => Type::list(Type::bool()), // will be overridden by the resolved type alias
]);

$this->assertEquals([
'Custom' => Type::int(),
'CustomArray' => Type::list(Type::bool()),
], $typeContextFactory->createFromClassName(Dummy::class)->typeAliases);

$this->assertEquals([
'Custom' => Type::int(),
'CustomString' => Type::string(),
'CustomInt' => Type::int(),
'CustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
'AliasedCustomInt' => Type::int(),
'PsalmCustomString' => Type::string(),
'PsalmCustomInt' => Type::int(),
'PsalmCustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
'PsalmAliasedCustomInt' => Type::int(),
], $typeContextFactory->createFromClassName(DummyWithTypeAliases::class)->typeAliases);
}

public function testDoNotCollectTypeAliasesWhenToStringTypeResolver()
{
$typeContextFactory = new TypeContextFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ public static function resolveDataProvider(): iterable
yield [Type::string(), 'CustomString', $typeContextFactory->createFromClassName(DummyWithTypeAliases::class)];
}

public function testResolveWithExtraTypeAlias()
{
$resolver = new StringTypeResolver(null, null, ['CustomAlias' => 'int']);

$this->assertEquals(Type::int(), $resolver->resolve('CustomAlias'));
}

public function testCannotResolveNonStringType()
{
$this->expectException(UnsupportedException::class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ final class TypeContextFactory
private ?Lexer $phpstanLexer = null;
private ?PhpDocParser $phpstanParser = null;

/**
* @param array<string, string> $extraTypeAliases
*/
public function __construct(
private readonly ?StringTypeResolver $stringTypeResolver = null,
private readonly array $extraTypeAliases = [],
) {
}

Expand Down Expand Up @@ -194,8 +198,10 @@ private function collectTypeAliases(\ReflectionClass $reflection, TypeContext $t
return [];
}

$extraAliases = array_map($this->stringTypeResolver->resolve(...), $this->extraTypeAliases);

if (!$rawDocNode = $reflection->getDocComment()) {
return [];
return $extraAliases;
}

$aliases = [];
Expand Down Expand Up @@ -229,7 +235,7 @@ private function collectTypeAliases(\ReflectionClass $reflection, TypeContext $t
$aliases[$tag->value->alias] = (string) $tag->value->type;
}

return $this->resolveTypeAliases($aliases, $resolvedAliases, $typeContext);
return $this->resolveTypeAliases($aliases, $resolvedAliases, $typeContext) + $extraAliases;
}

/**
Expand Down
14 changes: 12 additions & 2 deletions src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@ final class StringTypeResolver implements TypeResolverInterface
private readonly Lexer $lexer;
private readonly TypeParser $parser;

public function __construct(?Lexer $lexer = null, ?TypeParser $parser = null)
{
/**
* @param array<string, string> $extraTypeAliases
*/
public function __construct(
?Lexer $lexer = null,
?TypeParser $parser = null,
private readonly array $extraTypeAliases = [],
) {
if (class_exists(ParserConfig::class)) {
$this->lexer = $lexer ?? new Lexer(new ParserConfig([]));
$this->parser = $parser ?? new TypeParser($config = new ParserConfig([]), new ConstExprParser($config));
Expand Down Expand Up @@ -339,6 +345,10 @@ private function resolveCustomIdentifier(string $identifier, ?TypeContext $typeC
return $typeContext->typeAliases[$identifier];
}

if (isset($this->extraTypeAliases[$identifier])) {
return $this->resolve($this->extraTypeAliases[$identifier]);
}

throw new \DomainException(\sprintf('Unhandled "%s" identifier.', $identifier));
}
}
Loading