Continuous Learning

Dependency Injection: Constructor Injection vs. Setter Injection

Your might have heard of phrases like Dependency Injection, Constructor Injection and Setter Injection. These sound a bit confusing at first, but at the end, the concepts are very simple, but also very powerful.

Dependency Injection

First, let me explain the phrase Dependency Injection, because it's maybe the simplest one and you most likely already use it every day. Let's say we have a Validator class which somehow validates things. Now we also have a Translator class, which translates things somehow. As we know that separation of concerns is a good thing, they are decoupled - the Validator does know nothing about translating things and the Translator does know nothing about validating things. But we might also want to translate the things that were validated. So the Validator needs some help from the Translator - it depends on it or it has a dependency. To give the Validator access to the Translator we pass it in - or inject it. We inject a dependency. That's all what dependency injection is, passing objects to other objects.

Setter Injection

Now we have several ways to achieve this. We could simply pass it every time something is validated, for example $validator->validate($someObject, $translator), but this clutters the validate-method and is not very intuitive. Instead, we could store the dependency as a property of the Validator. Now to pass it in we simply use a setter like $validator->setTranslator($translator). After we have done this we can call the validate-method as often as we like and it will always use the passed Translator. The Validator now looks something like this:

class Validator
{
    private $translator;

    public function setTranslator($translator)
    {
        $this->translator = $translator;
    }

    public function validate($someObject)
    {
        // do the magic here with the help of $this->translator
    }
}

That is all that setter injection is. Now this is great for optional dependencies. In our example the validate-method should also work fine with no Translator set because there is no guarantee it has been set before the validate-method has been called!
But what if we want to enforce that?

Constructor Injection

If you have a dependency that you cannot leave away because your class does not work without it, constructor injection is the way to go. Instead of calling a set method, the dependency is passed via the constructor. Now there is no way around, but passing it when an object is created. This means for our example that we have to pass a Translator to the Validator in its constructor. This will look something like this:

class Validator
{
    private $translator;

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

    public function validate($someObject)
    {
        // do the magic here with the help of $this->translator
    }
}

Constructor Injection is great for obligatory dependencies. You clearly communicate to the user of that class: You have to pass me such an object, otherwise, I won't work.

So what should I use?

It absolutely depends on the use case. While in many situations a class simply does not work without its dependencies, it's perfectly fine to leave them away in other. For example, your Validator might not work without a Translator, because you always want to display the errors to your users. On the other hand it maybe also has a dependency on a Logger class which is able to write the errors to a log file or send it via email. But this might be completely optional.

Interfaces

At the beginning, I said that the Validator should know nothing about how to translate things. But now we have a hard dependency to the Translator. But what if we want to use a different implementation at some point? This might be more obvious with the example of a Logger, so let's talk about that one. So our Validator is also able to log its results to a log file. We used setter injection here to pass in a FileLogger:

class Validator
{
    private $logger;

    public function setLogger(FileLogger $logger)
    {
        $this->logger = $logger;
    }
}

That's fine - until we decide that we need to be informed about the results via email instead of having to look through the log files manually. Now we need to edit the Validator-class to pass in an EmailLogger instead. That's a bad thing because editing existing code always has the risk to break something. And after all, we just want to change the way we log things - why do we need to change a Validator for that?

Interfaces to the rescue: Instead of type hinting for the concrete implementation of the FileLogger in our Validator-class we type hint for the interface Logger. Now our FileLogger and EmailLogger simply implement that interface and we are free to change the logger we pass as often as we want - without changing a single line of the Validator code.

Author image
About Daniel Mecke