-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Symfony version(s) affected
6.2.5
Description
A controller using ServiceSubscriberTrait
is wired up correctly. However, when a request is served, its service locator is overwritten by the service container. This usually causes an error when a service is retrieved from the service container, since the service names are likely to be incompatible.
How to reproduce
Repo demonstrating the issue here: https://github.com/edsrzf/symfony_reproducer
To reproduce:
- Start the local server with
symfony server:start
- Navigate to http://127.0.0.1:8000
- See the error:
You have requested a non-existent service "App\Controller\Controller::getService". Did you mean this: "App\Controller\Controller"?
Possible Solution
The cause is this bit of code in Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver
:
symfony/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php
Lines 32 to 36 in e16aea4
if (null === $previousContainer = $controller->setContainer($this->container)) { | |
throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); | |
} else { | |
$controller->setContainer($previousContainer); | |
} |
when combined with setContainer
from ServiceSubscriberTrait
:
symfony/src/Symfony/Contracts/Service/ServiceSubscriberTrait.php
Lines 66 to 76 in 33377e7
#[Required] | |
public function setContainer(ContainerInterface $container): ?ContainerInterface | |
{ | |
$this->container = $container; | |
if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { | |
return parent::setContainer($container); | |
} | |
return null; | |
} |
setContainer
is called a total of 3 times in the process of serving a request:
- Once by the service container, passing in the service locator. It returns
null
. - The first time by
ControllerResolver
, passing in the service container. The trait sets thecontainer
property, and thenAbstractController
returns the "previous" container property value, which is actually not the previous value. It's the service container. - The second time by
ControllerResolver
, which passes the "previous" value back in. It should be the service locator, but it's actually the service container.
It feels like the fix needs to be inside ServiceSubscriberTrait
. Maybe parent::setContainer
could be called before setting $this->container
?
Additional Context
Workaround
The issue can be worked around by overriding setContainer
, skipping the trait's method:
#[Required]
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
return parent::setContainer($container);
}