in PHP

Avoiding Setter Injection

PHP more or less has two kinds of dependency injection available: constructor injection, and setter injection.

Constructor injection is the process of injecting dependencies through the constructor arguments, like so:

<?php

class MyClass
{
    public function __construct(SomeClass $someClass, SomeDep $dep)
    {
        // do something
    }
}

The dependencies are injected via the constructor, on object creation, and the object has them from the very beginning.

Setter injection is different; instead of providing the dependencies at construction time, the dependencies are provided via setter methods, once the object has been constructed. This allows the flexibility to configure the object during the runtime, rather than at construction.

Setter injection suffers from two flaws: first, it can lead to objects that are half-baked when they are constructed. Second, it can lead to objects that receive objects, but do not use them during the routine of that object.

Half-baked objects upon construction.

Objects should be fully configured when they are constructed; this means that their dependencies, defaults and configurations are in place and the object is ready to do the work for which it was created.

Sometimes setter injection can be used to configure an object after it has been instantiated. This process can be due to many reasons: a need to configure an object at runtime, or simple laziness on the part of the developer. Whatever the reason, this practice should be discouraged; inevitably, the object being created will be used in it’s half-formed state, with disastrous consequences.

That’s not to say that an object cannot accept and replace a default object passed into the constructor. It’s perfectly acceptable to construct an object with sane defaults, then replace its component parts later on. Be careful, however, that you don’t violate other rules of object-oriented design, like the Law of Demeter or the Single Responsibility Principle.

Objects provided go unused during the routine

This is a common problem in controllers: a controller balloons in size to the point where it has half a dozen setters, all of which are called by a dependency injection container but few of which are used on each individual request. The controller object has to learn about each of these dependencies, yet it may not need each dependency in a particular routine.

Objects should be clean and they should only know the dependencies that they need to know (Law of Demeter). The controller that I have described is not object-oriented programming; it’s procedural programming using classes.

It’s important to build objects carefully, providing them limited information about the outside world, and being careful to only provide them their “friends”. Objects that a routine does not require should not be provided to an object, and if you find yourself providing objects for a routine that sometimes runs, start looking at opportunities to break that up into smaller components.

Avoid setter injection.

Setter injection can be tempting for the flexibility it offers, but should be avoided for the pitfalls that can happen when it’s used improperly. It’s easy to avoid with careful construction of objects, and in my applications I avoid it almost exclusively. I recommend that you do the same.

Frustrated with your company’s development practices?

You don't have to be!

No matter what the issues are, they can be fixed. You can begin to shed light on these issues with my handy checklist.

Plus, I'll help you with strategies to approach the issues at the organization level and "punch above your weight."

Powered by ConvertKit

Write a Comment

Comment

  1. Actually, you can pass a class through a method (See Misko Hevery pdfs about testable code.)
    If injected class has a lifespan equal or longer the class, inject it through the constructor.
    If injected class has a lifespan shorter than the class, inject it through the method.

  2. I use setter injections for things that are initialized to default values most probable for my use cases. These are almost always scalar values (ex. some flags) or no-op objects (like NullLogger). That way I’m not bloating the constructor but still allows for flexibility in those rare cases.

    If I’m afraid, that such setter can be (ab)used later in the code changing the class behaviour in the middle of processing, I “seal” the object for changes after its construction and configuration (all setters check if the object is sealed). I’ve got handy trait for just that.

    And yeah, method injection is also a thing.