-->

How to access not-injected services directly on Sy

2020-05-10 19:25发布

问题:

I'm trying to update Symfony 2.8 to Symfony 4 and I am having serious problems with the Services Injection.

I'm looking the new way to use Services inside Controllers, with auto-wiring:

use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
    public function index(AuxiliarService $service)
    {
        $var = $service->MyFunction();
        ....

This way works fine, but I dislike the explicit way to refer MyService as a parameter of the function. This way I don't even need to register the Service in the services.yaml

Is any way to use Services as in Symfony 2.8:

class DefaultController extends Controller
    {
        public function index()
        {
            $var = $this->get('AuxiliarService')->MyFunction(); /*Doesn't need to be explicit indicate before*/
            ....

With the services.yaml

services:
    auxiliar_service:
        class:        AppBundle\Services\AuxiliarService
        arguments: 
            entityManager: "@doctrine.orm.entity_manager"
            container: "@service_container" #I need to call services inside the service

This way I don't need to indicate the Service as a parameter in the function of the Controller. In some cases, inside a Service, I need to call more than 10 services depends on the data, so indicate them as a parameter in the function is annoying.

Another doubt in Symfony 4, is how to call a Service inside another Service without pass it as an argument or parameter. It used to be possible by injecting the service container to be able to call a service inside a service:

$this->container->get('anotherService')

In Symfony 4, I think it is more expensive (in code) use Service because you have to explicitely indicate them when you are going to use them.

回答1:

tldr; you can achieve that by using Service Subscribers & Locators.

In your controller:

use App\Service\AuxiliarService;
class DefaultController extends AbstractController
{
    public function index(AuxiliarService $service)
    {
        $var = $service->MyFunction();
    }

    public static function getSubscribedServices()
    {
        return array_merge(parent::getSubscribedServices(), [
            // services you want to access through $this->get()
            'auxiliar_service' => AuxiliarService:class,
        ]);
    }
// rest of the implementation
}

If your service needs to implement a similar pattern, you'll need to implement ServiceSubscriberInterface (AbstractController, that you are extending for your controller, already does that for you).

class AuxiliaryService implements ServiceSubscriberInterface
{
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    protected function has(string $id): bool
    {
        return $this->container->has($id);
    }

    protected function get(string $id)
    {
        return $this->container->get($id);
    }

    public static function getSubscribedServices()
    {
        return [
            // array_merge is not necessary here, because we are not extending another class. 
            'logger' => LoggerInterface::class,
            'service2' => AnotherService::class,
            'service3' => AndMore::class
        ];
    }
}

That being said, you are very probably not doing things right if you want to continue this way

Before Symfony 4+ you could do $this->get('service') because these controllers all had access to the container. Passing the dependency container around for this it is an anti-pattern, and shouldn't be done.

If you do not declare your dependencies, your dependencies are hidden. Users of the class do not know what it uses, and it's easier to break the system by changing the behaviour of one of the hidden dependencies.

Furthermore, with Symfony providing auto-wiring and a compiled container; dependency injection is both easier to implement and faster to execute.

That you are having trouble with implementing this probably reveals deeper issues with your code in general, and you should do some work on segregating the responsibilities of your classes. The fact that one service may depend on that many other services which you can't even know until runtime it's a very strong smell that the concerns are not well separated.

Try to adapt to the changes, it will do your application and yourself good in the long term (even if brings a small amount of pain right now).