New in Symfony 4.1: Autowiring improvements

Contributed by
Ryan Weaver
in #26658.

Allow binding scalar values to controllers

When using service autowiring, the _defaults.bind option allows to bind arguments by name or type. For example, you can define the value of $projectDir once in your app and every service that uses a constructor argument with that name will use its value:

1
2
3
4
5
# config/services.yaml
services:
    _defaults:
        bind:
            $projectDir: '%kernel.project_dir%'

However, if some controller action defines a $projectDir argument, this configuration doesn't apply to it and the argument is not autowired:

1
2
3
4
5
6
7
/**
 * @Route("/do-something")
 */
public function somethingAction($projectDir)
{
    // the $projectDir argument is not given the configured value
}

In controllers, you needed to use the $this->getParameter('kernel.project_dir') shortcut or pass the value through the controller's __construct() method. This behavior didn't provide a consistent experience (service constructor binding behaved differently than controller action binding) and it was the last rough edge about autowiring.

That's why in Symfony 4.1 you can bind scalar values to controller arguments. Define the binding as usual in _defaults.bind and then add the arguments to the controller actions (there's no need to define a constructor method for them).

Service decoration autowiring

Contributed by
Kévin Dunglas
in #25631.

Service decoration allows to change the behavior of a service without affecting to the other uses of the original service. When decorating services, the config requires to pass the decorated service as an argument using a special naming syntax:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# config/services.yaml
services:
    # this is the service you want to decorate
    App\Mailer: ~

    App\DecoratingMailer:
        decorates: App\Mailer

        # you must pass the original service as an argument, and its name
        # is: "decorating service ID" + ".inner"
        arguments: ['@App\DecoratingMailer.inner']

Although explicit, this config looks like an internal Symfony detail. That's why in Symfony 4.1 the inner service is automatically autowired if possible. This is the new config for the same example as before:

1
2
3
4
5
6
# config/services.yaml
services:
    App\Mailer: ~

    App\DecoratingMailer:
        decorates: App\Mailer

The automatic configuration of the decorated service is done when the following conditions are met:

  1. The decorating service is autowired;
  2. The constructor of the decorating service has only one argument of the type of the decorated service.

Comments

Controller argument's autowiring and bindings are a pure hell to me.

Like when you need the User, if you have only one user registered in the "memory" provider, it'll be linked everytime, even when user is not authenticated.

Bindings can overwrite route parameters (or the opposite) because everything in $request->attributes CAN be injected in controllers.

I think this system spreads bad practices and uncertainties everywhere.

DI is about dependencies. A route action is tightly coupled with routing. A controller constructor must therefore be coupled with DI.
"I think this system spreads bad practices and uncertainties everywhere."

Same for me :/
@Alex Rock Ancelet
Agreed.

Not that I don't appreciate the effort, but in my opinion, it is good practice to separate DI and Routing.
@Alex Rock Ancelet
Agreed.

I think mixing DI into the routing make it confusing with some routes actions containing both in complete disorder. Never knowing where the data comes from. Having dependencies coupled with the controller constructor make it much easier to read.
Hey guys!

I appreciate the comments about the controller DI :). The thing is, we really wanted to make controllers as services something that *everyone* could use, which is a great step froward. This trick - which is if course totally optional - helped us with that effort. The problem with controllers as services (really, the only problem) is that, to many people - especially when just getting started - the requirement to use DI through the constructor is cumbersome. The action-injection helps lower that barrier-to entry, while still allowing them to use controllers as services.

Like with almost everything, these types of features allow us to lower the barrier to entry, but hopefully also to give people a smooth option to update their code later, if/when they choose to prefer constructor injection. Personally, I use the action injection everywhere and love it. But, that's why Symfony is great - you can opt into this shortcut or not, and grow out of it as you learn more about your options :).

Cheers!
Login with SensioLabsConnect to post a comment