Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 2.8 Autowiring for method injection

Update 2020 (this should have been much sooner). There can be a new class for each controller action. Each controller can be named by the action it is going to do, with an Invoke() method. Consider "Action Domain Responder" (ADR).

Why do I want to do this? Controllers do not necessarily adhere to SRP, and I'm not about to go creating a new class for each of, what is effectively, a controller 'action'. Therefore, a controller should not have everything injected via constructor, but the relevant method being called should be able to state explicitly "I require these objects" and another method "these other objects".

As of Symfony 2.8, the Dependency Injection component now provides auto-wiring for your services if you provide autowire: true in services.yml.

I'm defining my controller as a service, like so:

test_controller:
    class: AppBundle\Controller\TestController
    autowire: true

My controller looks as follows:

class TestController
{
    public function indexAction(TestClass1 $tc1, $id)
    {
        return new Response('The slug is: ' . $id);
    }
}

You will notice I'm typehinting for TestClass, which is just an empty class. However, the following error appears when I refresh the page:

Method Injection Symfony Autowiring Error

What do I need to change in my services.yml file to have auto wiring dependency injection in my controller method? Just a note, the issue isn't because I have an $id 'slug' afterwards. Removing it does nothing.

like image 418
Jimbo Avatar asked Sep 25 '22 15:09

Jimbo


1 Answers

Edit: I've created a bundle allowing to do more or less what you want called DunglasActionBundle.

Disclaimer: I'm the author of the Symfony autowiring system.

The Symfony Dependency Injection Component (and the autowiring system is part of it) doesn't work that way. It only allows to automatically inject dependencies in the class constructor of services and knows nothing about controller classes, actions and other parts of the HttpKernel Component.

It's not currently possible to do what you want to do. Initially, the autowiring system has been designed for domain services, not for controllers.

It should be possible to bridge the autowiring system with controller parameters using a custom param converter. However, I'll suggest you take another way I've first described here:

There is another approach I want to discuss since several time but I did not have the time to blog about it.

It's a derivate of/similar to the ADR pattern, applied to Symfony.

  1. An action is a single class with a __invoke() method containing all it's logic (should be only some lines of glue code to call the domain and the responder).
  2. Actions are registered as a service (it can be automated using a compiler pass and some conventions, similar to how we discover controllers right now, with the ability to override the definition if needed)
  3. On such action services, autowiring is enabled.

It means that almost as easy as current controllers to use for the developper, but with an explicit dependency graph and a better reusability. Only things that are really necessary are injected. In fact, it's already doable to use such system with Symfony. We do it in API Platform master. Here is one action for instance: https://github.com/dunglas/DunglasApiBundle/blob/master/Action/PostCollectionAction.php

In this implementation I also rely a lof on kernel events to be able to plug and play some sort of logic (persistence, validation...) in a decoupled manner but it's out of scope.

like image 70
Kévin Dunglas Avatar answered Sep 29 '22 06:09

Kévin Dunglas