Symfony 4 Autowiring in a Nutshell

Symfony moved into version 4 last November. One of the best patterns embraced by the newest major version of this venerable framework is Autowiring — basically a term for Dependency Injection that uses PHP type-hinting.

What is Dependency Injection?

A fine place to start!

To summarize Dependency Injection as simply as possible: When a class needs another class, the framework should provide it. The developer only needs to handle the configuration and code of those classes.

When a class needs another class, the framework should provide it.

These classes are called “Services”. In Symfony, when you want to make some code available for use by other classes, you put it in a Service. Symfony makes it easy to provide these Services to the rest of your application.

My favorite line from the Wikipedia article describing this pattern is:

“This allows the client to make acquiring dependencies someone else’s problem.”

That’s exactly what it’s about. When you make a Service, the dependencies for that Service is Symfony’s problem, and it will deliver.

Where Autowiring comes in

Say you’ve written several useful Services to do things in your application. How do they get injected where they are needed? How do you use them in a Controller, for example?

This is where Autowiring comes in. PHP can use type-hints in function arguments. While not a strongly-typed language, PHP can still clue in on what type a variable should be. If a developer needs a Service or Controller action to always receive the Symfony Translator Service, that developer could type-hint the function to use TranslatorInterface on the argument. Symfony uses these type-hints to inject the correct service when it’s needed.

In a Controller action, for example, if you add a class (or interface) as a type-hint to an argument, Symfony can read that and inject the Service that matches the type-hint.

/**
* @Route("/movie/{id}", name="movie_details")
*/
public function movieDetailsAction(Movie $movie, CommentRepository $commentRepository)
{
$comments = $commentRepository->findBy([ 'movie' => $movie ], [ 'createdAt' => 'DESC' ], 5);

return $this->render('movie.html.twig', [
'comments' => $comments,
'movie' => $movie,
]);
}

The above code represents a controller action. The arguments to the action are type-hinted using both Movie and CommentRepository. Symfony automatically instantiates and injects the CommentRepository. We can then use that in our action to query for the last 5 comments left for the movie.

Okay, this works for Controllers, but what about custom Services? Here’s a pattern I use all the time:

<?php

namespace App\Viewmodel;

use App\Entity\Movie;
use Symfony\Component\Routing\RouterInterface;

class MovieViewmodel
{
private $router;

public function __construct(RouterInterface $router)
{
$this->router = $router;
}

public function makeViewmodel(Movie $movie)
{
return [
'title' => $movie->getTitle(),
'url' => $this->router->generate('movie_details', [ 'id' => $movie->getId() ]),
];
}
}

Our service is called MovieViewmodel, which has a function to convert a movie into a plain associative array that we’ll use for rendering later (this also makes transformations testable). Our Service relies on another Symfony component though — the router.

With Autowiring, this dependency is trivial. In our constructor, we make a $router argument and type-hint it with the service we need, in this case, RouterInterface. When Symfony uses this class, the service is automatically constructed with the router. That means this class now has the power to generate URLs to other routes in our app.

So why is this pattern good?

On-demand Services.

Think about that. When your PHP app is running, it only creates Services when they are needed.

Only when Symfony encounters a type-hint for a Service does it actually construct that Service. That leads to a performant application!

Not only that, but Dependency Injection is awesome for testing. When testing a Service, the developer can contrive whatever is needed to fit the dependencies of that Service. Symfony already has the wonderful KernelTestCase class for testing things that rely on the Symfony container (e.g. Routing and Translation).

Testable Services lead to robust software.

But because you can inject whatever is appropriate for your tests into your Services, you have to ability to mock, facade, or stub out whatever creates predictable test cases. Testable Services lead to robust software.

See available type-hintable Services

Using the command line tool, run the command “php bin/console debug:autowiring” to see all the type-hintable Services available in your application.

Want to know more? Check the docs.