Skip to content

ServiceSubscriberTrait doesn't fully support PHP8 union types #43913

Closed
@gmichard

Description

@gmichard

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

  1. Create a brand new Symfony Application (download documentation)
    symfony new --full my_project
  2. Run the application
  3. Add a new service that use the ServiceSubscriberTrait and that have a method with more that one type for return type. See the following ExampleHandler class.
  4. 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 { …}
  }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions