-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Description
Symfony version(s) affected
7.3.3
Description
We are using JMS Translation bundle to extract translations, which uses Validator to acquire assertion messages. This in turn causes PropertyInfo component to work and extract them from phpdoc's using new TypeInfo.
In our code we use @phpstan-type and @template for some classes. This recently caused our translation extraction to fail, and we investigated.
In TypeContextFactory.php line 275:
[Symfony\Component\TypeInfo\Exception\LogicException]
Cannot resolve "TIOHandlersMap" type alias.
Exception trace:
at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/type-info/TypeContext/TypeContextFactory.php:275
Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->resolveTypeAliases() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/type-info/TypeContext/TypeContextFactory.php:234
Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->collectTypeAliases() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/type-info/TypeContext/TypeContextFactory.php:74
Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->createFromClassName() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/property-info/Extractor/PhpStanExtractor.php:209
Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor->getType() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/property-info/PropertyInfoExtractor.php:70
Symfony\Component\PropertyInfo\PropertyInfoExtractor->getType() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php:193
Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader->getPropertyTypes() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php:70
Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader->loadClassMetadata() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Mapping/Loader/LoaderChain.php:48
Symfony\Component\Validator\Mapping\Loader\LoaderChain->loadClassMetadata() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php:96
Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory->getMetadataFor() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Validator/RecursiveValidator.php:70
Symfony\Component\Validator\Validator\RecursiveValidator->getMetadataFor() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/symfony/validator/Validator/TraceableValidator.php:48
Symfony\Component\Validator\Validator\TraceableValidator->getMetadataFor() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/jms/translation-bundle/Translation/Extractor/File/ValidationExtractor.php:87
JMS\TranslationBundle\Translation\Extractor\File\ValidationExtractor->enterNode() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:196
PhpParser\NodeTraverser->traverseArray() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:98
PhpParser\NodeTraverser->traverseNode() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:227
PhpParser\NodeTraverser->traverseArray() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php:76
PhpParser\NodeTraverser->traverse() at /home/stevebpn/PhpstormProjects/ibexa/5.0.x-dev-commerce/vendor/jms/translation-bundle/Translation/Extractor/File/ValidationExtractor.php:109
JMS\TranslationBundle\Translation\Extractor\File\ValidationExtractor->visitPhpFile() at n/a:n/a
☝️ as you can see, TypeInfo is fed information from PhpStanExtractor. With a specific setup this causes issues.
How to reproduce
To make the issue show up, set up a new Symfony project and install additional packages to trigger PhpStanExtractor to become available for validation metadata.
symfony new test-validator-metadata
symfony composer require validator symfony/property-info phpdocumentor/type-resolver phpstan/phpdoc-parser Then create a following class to contain phpdoc-based metadata:
<?php
# src/Test/Collection.php
namespace App\Test;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* @template TCollectionType of object
*
* @phpstan-type TCollectionTypeMap array<string, TCollectionType>
*/
final class Collection
{
/**
* Map of a handler id to a handler service instance.
*
* @phpstan-var TCollectionTypeMap
*/
private array $handlersMap = [];
/**
* @phpstan-param TCollectionTypeMap $handlersMap
*/
public function setHandlersMap(array $handlersMap): void
{
$this->handlersMap = $handlersMap;
}
/**
* @phpstan-return TCollectionType
*/
public function getConfiguredHandler(string $handlerName): object
{
if (!isset($this->handlersMap[$handlerName])) {
throw new InvalidConfigurationException("Unknown handler $handlerName");
}
return $this->handlersMap[$handlerName];
}
}To trigger the issue I've used a following controller:
<?php
# src/Controller/TestController.php
declare(strict_types=1);
namespace App\Controller;
use App\Test\Collection;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
final class TestController extends AbstractController
{
public function __construct(
#[Autowire(service: 'validator.mapping.class_metadata_factory')]
private readonly MetadataFactoryInterface $metadataFactory,
) {
}
#[Route('/test')]
public function __invoke(): Response
{
$this->metadataFactory->getMetadataFor(Collection::class);
return new Response();
}
}Calling this controller will result in an exception when trying to read metadata for Collection:
With the following stacktrace from the innermost exception:
DomainException:
Unhandled "TCollectionType" identifier.
at vendor/symfony/type-info/TypeResolver/StringTypeResolver.php:342
at Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver->resolveCustomIdentifier()
(vendor/symfony/type-info/TypeResolver/StringTypeResolver.php:208)
at Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver->getTypeFromNode()
(vendor/symfony/type-info/TypeResolver/StringTypeResolver.php:252)
at Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver->Symfony\Component\TypeInfo\TypeResolver\{closure}()
at array_map()
(vendor/symfony/type-info/TypeResolver/StringTypeResolver.php:252)
at Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver->getTypeFromNode()
(vendor/symfony/type-info/TypeResolver/StringTypeResolver.php:88)
at Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver->resolve()
(vendor/symfony/type-info/TypeContext/TypeContextFactory.php:262)
at Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->resolveTypeAliases()
(vendor/symfony/type-info/TypeContext/TypeContextFactory.php:232)
at Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->collectTypeAliases()
(vendor/symfony/type-info/TypeContext/TypeContextFactory.php:74)
at Symfony\Component\TypeInfo\TypeContext\TypeContextFactory->createFromClassName()
(vendor/symfony/property-info/Extractor/PhpStanExtractor.php:209)
at Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor->getType()
(vendor/symfony/property-info/PropertyInfoExtractor.php:70)
at Symfony\Component\PropertyInfo\PropertyInfoExtractor->getType()
(vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php:193)
at Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader->getPropertyTypes()
(vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php:70)
at Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader->loadClassMetadata()
(vendor/symfony/validator/Mapping/Loader/LoaderChain.php:48)
at Symfony\Component\Validator\Mapping\Loader\LoaderChain->loadClassMetadata()
(vendor/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php:96)
at Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory->getMetadataFor()
(vendor/symfony/validator/Validator/RecursiveValidator.php:70)
at Symfony\Component\Validator\Validator\RecursiveValidator->getMetadataFor()
(src/Controller/TestController.php:25)
at App\Controller\TestController->__invoke()
(vendor/symfony/http-kernel/HttpKernel.php:183)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw()
(vendor/symfony/http-kernel/HttpKernel.php:76)
at Symfony\Component\HttpKernel\HttpKernel->handle()
(vendor/symfony/http-kernel/Kernel.php:182)
at Symfony\Component\HttpKernel\Kernel->handle()
(vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35)
at Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run()
(vendor/autoload_runtime.php:29)
at require_once('/home/stevebpn/PhpstormProjects/ibexa/test-validator-metadata/vendor/autoload_runtime.php')
(public/index.php:5)
Possible Solution
It seems that Symfony\Component\TypeInfo\TypeContext\TypeContextFactory::resolveTypeAliases() is unaware of any @template declarations that might be present in the same file (extracted by Symfony\Component\TypeInfo\TypeContext\TypeContextFactory::collectTemplates()). It might be possible that types could be properly resolved if resolveTypeAliases was made aware of @template types).
Additional Context
No response