What Dependencies Should be Injected into a Controller?

This article ist mostly about Symfony, but the advice here applies across frameworks.

It’s easier to define what’s shouldn’t be injected as a dependency: things global to the framework or the application being built.

A form system is something global to the framework/application. Should the FormFactory be injected into every controller? How about templating? Should a template compiler be injected into every controller?

I wouldn’t. Instead use the global functions provided by your framework or something like Symfony’s service subscriber system — extending Symfony’s AbstractController provides this by default.

Similarly, something like a command bus is global to the application itself. A command bus could be provided to all controllers via a service subscriber (or something similar), but that’s slightly more questionable as it gives every controller a lot of power it may not need. Compared to templating or forms or serialization which are things most controllers do all the time.

Inject Things Specific to the Controller

Returning a list of users? Put a UserRepository in the controllers constructor. Putting some job into the queue for later processing? Require the queue producer in the controllers constructor.

These are things specific to the controller itself rather than general to the application as a whole. Those things should be injected.

One of the things I like about the service subscriber/locator thing in Symfony is that the locator given to controllers has a very small surface area. It’s not the complete container, but instead a limited subset of things the controller needs to do its job. This allows the contructor arguments to remain focused on the specific things the controller does.

I’ve been on both extremes of this stuff. Symfony prior to 3.X recommended using the container exclusively in controllers. I’ve got a few applications that still do this. It’s easy to develop controllers, but also very hard really know where things actually are. They could be anywhere because any controller can do anything.

On the other had, I’ve had a few appliations that injected absolutely everything into controller constructors. This was a way I migrated a Silex application to Symfony full stack and I did it again on a fresh appliation based on Symfony 4. Injecting everything means spending a lot of times re-building the abstraction provided by Symfony’s AbstractController. Maybe that’s not a bad thing, but it’s certainly a lot of work a framework can do instead.

This is also coming from a place where I do not unit test controllers. Mostly because it does not matter if a controller works in isolation. It’s literally the point at which a framework and an application built with that framework meet. If a controller doesn’t work with the framework it doesn’t work at all. I’d rather keep my controllers short and free of (most) logic and hit them with end-to-end tests.