问题
I am trying to use the container.service_subscriber
tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring
and also can't use the autoconfigure option.
The structure of the Controller is as follow:
I have a base BaseController
which extends from the AbstractFOSRestController
of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent
for my other Controllers.
The service definition looks like this:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "@service1"
- "@service2"
- ...
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
#autowire: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
The class looks like this:
/**
* User controller.
*/
class UserController extends AbstractCRUDController implements ClassResourceInterface
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'servicexyz' => ServiceXYZ::class,
]);
}
.......
}
The problem I have is, if I set autowire: false
, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):
User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.
When setting autowire: true
Symfony does respect the container.service_subscriber
tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?
回答1:
The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.
# ApiBundle/Resources/config/services.yaml
services:
_defaults:
autowire: false
autoconfigure: false
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
class UserController extends AbstractController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// ...
'logger' => LoggerInterface::class,
]);
}
public function index()
{
$url = $this->generateUrl('user'); // Works as expected
// $signer = $this->get('uri_signer'); // Fails as expected
$logger = $this->get('logger'); // Works as expected
return new Response('API Index Controller ' . get_class($this->container));
}
}
Results in:
API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator
Indicating that a service locator (as opposed to the global container is being injected).
You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
calls: [['setContainer', ['@Psr\Container\ContainerInterface']]]
回答2:
Solution to the problem is to extend the service definition of the Controller with a call to setContainer
to inject the '@Psr\Container\ContainerInterface'
service:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "@service1"
- "@service2"
- ...
calls:
- ['setContainer', ['@Psr\Container\ContainerInterface']]
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
This will give me a ServiceLocator
as container containing only the regiestered services instead of the full container without using the autowire
option.
Sidenote: Setting the @service_container
would inject the full container.
For completeness, there was already an issue on the symfony project where this was discussed.
来源:https://stackoverflow.com/questions/60078704/adding-services-to-a-controller-through-container-service-subscriber-not-worki