Description
Symfony version(s) affected
5.3.10
Description
An error occurs when using the Service Subscriber Trait on a service class that have a method with more than one type (i.e. union type) for return type.
Call to undefined method ReflectionUnionType::isBuiltin() at /vendor/symfony/service-contracts/ServiceSubscriberTrait.php:45
How to reproduce
- Create a brand new Symfony Application (download documentation)
symfony new --full my_project
- Run the application
- Add a new service that use the
ServiceSubscriberTrait
and that have a method with more that one type for return type. See the followingExampleHandler
class. - Clear your cache, run the symfony
bin/console
<?php
// File: src/Service/ExampleHandler.php
namespace App\Service;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;
class ExampleHandler implements ServiceSubscriberInterface
{
use ServiceSubscriberTrait;
protected function getSomethingOrOther(): UserAuthenticatorInterface|UserInterface|null
{
// No body. Return null for example. Only the return type is important for this issue.
return null;
}
}
With this example, if I add a xDebug breakpoint on the line 45 of ServiceSubscriberTrait
with the condition of suspension:
self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !method_exists($returnType, 'isBuiltin')
(Note the !method_exists($returnType, 'isBuiltin')
)
I can check the values of $method
and $returnType
// xDebug evaluation of two variables on line 45:
$method = ReflectionMethod::__set_state(array(
'name' => 'getSomethingOrOther',
'class' => 'App\\Service\\ExampleHandler',
));
// The method "getSomethingOrOther" is the cause of the error.
$returnType = ReflectionUnionType::__set_state(array(
))
// The return type is an instance of `ReflectionUnionType`
// which have no method called "isBuiltin".
Possible Solution
-
We can see in the PHP ReflectionType class "Changelog" section that:
(Version 8.0.0) ReflectionType has become abstract and ReflectionType::isBuiltin() has been moved to ReflectionNamedType::isBuiltin().
-
We can see that the issue [3.4] Fix support for PHP8 union types #37340 by @nicolas-grekas mentions the "PHP8 Union types" and the
isBuilin
method.
But the modifications doesn't seem to fix this use case.The reason is that these might return a ReflectionUnionType now, which has no getName() method (same for `isBuiltin() btw.)
Maybe a test is missing before calling the isBuiltin
(like $returnType instanceof ReflectionNamedType
), but I'm not sure about the expections here and the eventual side-effects.
Additional Context
Error trace in console.
Symfony\Component\ErrorHandler\Error\UndefinedMethodError^ {#25
#message: "Attempted to call an undefined method named "isBuiltin" of class "ReflectionUnionType"."
#code: 0
#file: "./vendor/symfony/contracts/Service/ServiceSubscriberTrait.php"
#line: 45
trace: {
./vendor/symfony/contracts/Service/ServiceSubscriberTrait.php:45 { …}
./vendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php:74 { …}
./vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php:81 { …}
./vendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php:36 { …}
./vendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php:46 { …}
./vendor/symfony/dependency-injection/Compiler/Compiler.php:91 { …}
./vendor/symfony/dependency-injection/ContainerBuilder.php:749 { …}
./vendor/symfony/http-kernel/Kernel.php:545 { …}
./vendor/symfony/http-kernel/Kernel.php:786 { …}
./vendor/symfony/http-kernel/Kernel.php:125 { …}
./src/Kernel.php:19 {
App\Kernel->boot()^
› {
› parent::boot();
› }
}
./vendor/symfony/framework-bundle/Console/Application.php:168 { …}
./vendor/symfony/framework-bundle/Console/Application.php:74 { …}
./vendor/symfony/console/Application.php:167 { …}
./bin/console:43 { …}
}
}