Constructor Injection vs Setter Injection
Introduction
The argument in favour of setter injection is usually that it frees up the constructor to deal with specific object configuration. In a lot of cases, the dependencies will be the same for every instance of a class. A database object, for instance, will likely be shared between every instance of a class that requires it as a dependency. Alternatively, setter injection advocates praise the flexibility that it offers, the object can be reconfigured by re-injecting a dependency at any time during the object's lifecycle.
Firstly, I want to address that last point in detail because it's important. Being able to re-configure a dependency at any point during the object's life is not actually a good thing!
Why? Because it breaks encapsulation. Arbitrary points in the application shouldn't be aware of what dependencies any collaborators have. If an object has been passed into a method or another object in its constructor, the client code calling $obj->setDependency($dep) in order to reconfigure $obj should not actually be aware of what dependencies $obj has (or doesn't have). Wanting re-configure $obj by re-injecting a dependency into it does exactly that, whatever is doing the re-injection now knows that $obj has a specific dependency. It breaks encapsulation by exposing the internal implementation of a collaborator to the client code. In this case, whatever is calling $obj->setDependency($dep); is aware that the object $obj has a specific dependency, $dep.
The problem with this knoweledge being leaked into client code is that my example $obj object, like any object, should be able to have its implementation completely re-written while keeping the same API without having any effect on code which is making use of it. The best example of this is mock objects in TDD. That new implementation may not require the same dependencies. However, if the client code is calling a setDependency() method on the object, and a different implementation of the object does not require the dependency, why should it require the method for injecting it? By exposing the setDependency() method in the API you're making the assumption that every potential implementation of $obj will have that specific dependency. Consider for a moment the benefit of having a setDependency() method declared in an interface and you'll see why this is a bad idea. By doing so, you're making assumptions about how (and likely where) the interface is going to be used, and making assumptions about the classes which implement it. The same is true of a setDependency() method on a class as you're assuming subclasses, mocks, and other objects with the same interface will have the same dependencies.
This is all purely theoretical, of course, and the associated risks of setDependency() I've highlighted so far will only limit flexibility as an application grows. If you're a pragmatist this theoretical flaw probably doesn't concern you. However, from a purely practical perspective, a setDependency() method creates a very real problem:
If an arbitrary piece of code changes a dependency of an object by injecting a replacement using $object->setDependency($foo), it's going to have knock on effects on every other piece of code using the same instance of $object. Imagine if an arbitrary piece of code changed your DataMapper's database dependency at an unexpected point during the program's execution. It will inadvertently affect otherwise totally unrelated parts of the system because they're sharing the DataMapper object and that object has had its dependency changed unexpectedly. Suddenly everywhere that's using the same DataMapper instance will be reading/writing to a different database. A side effect that probably wasn't intentional. The easiest way to prevent that happening is to disallow it from becoming a problem in the first place by avoiding the existence of the setDatabase() (or similar) method on the DataMapper.
Because of encapsulation, the argument of being able to re-configure the dependencies after the fact is not a valid reason for using setter injection. It's actually an argument against it because it directly exposes internal implementation by giving client code the ability to alter its state and as such, breaks encapsulation and by extension makes it possible for dependency re-configuration at runtime to cause unexpected knock-on effects elsewhere in the application.
Practical reasons for setter injection
As I mentioned earlier, The other main reason for setter injection is practicality. Keeping the constructor tidier and allowing the dependencies to be supplied in any order using method calls.
The problem with this is also that it breaks encapsulation. In practical terms that means it's possible to have an object in the system which has been successfully constructed but is still incomplete and not in a state where it's able to fulfil its responsibility. Consider this:
class Foo {
private $dep;
public function setDependency(SomeDependency $dep) {
$this->dep = $dep;
}
public function someMethod() {
$baz = $this->dep->xx();
}
}
class Bar {
private $foo;
public function __construct(Foo $foo) {
$this->foo = $foo;
$this->foo->someMethod();
}
}
$foo = new Foo();
$bar = new Bar($foo);
//This will error, although Foo looks like it's been constructed it's still incomplete.
The problem here is that Bar::__construct() has be given a fully constructed Foo object. When this happens, Bar should be able to assume the Foo object is complete and able to fulfil its responsibility. However, there's ambiguity here. Bar cannot make the assumption that Foo is fully constructed because there's no way to know whether the setDependency() method has been called or not.
This ambiguity is caused by broken encapsulation: the Bar::__construct() method will break or work based on the internal state of the Foo object. This kind of ambiguity causes bugs which are difficult to track down and test. The test case with a mock Foo object will work perfectly, but in production code where Bar::__construct() gets passed a Foo object which hasn't had Foo::setDependency() called and it will break.
Making it possible for incomplete objects to exist after they've been constructed is a very bad idea. It opens the whole application up to bugs which can easily be made impossible to even exist in the first place. By using Constructor Injection instead, these type of errors are 100% avoidable and will make your software more robust as a result.
Should setter injection be avoided at all costs?
Like (almost) everything, there is a time and a place. The time for setter injection is when the dependency is not required for core functionality and the constructed object can fulfil its responsibility without it (Whether that counts as a "Dependency" or not is up for debate, but it's certainly a collaborator.)
A good example is a logger
class Foo {
private $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function someMethod() {
if ($this->logger) $this->logger->log('someMethod called');
}
}
Here, the logger object is not required for the Foo object to do its job. As such, omiting asking for the Logger in the constructor does not cause an issue with an incomplete object existing in application.
It still suffers breaking encapsulation but a less problematic level. The client code knows that a Foo object can be passed a logger. But at the same time, if the logger is replaced, it's not going to break any core functionality in other objects which are sharing the Foo service so doesn't cause as much of an issue. Although it might leave someone wondering why their logs are incomplete.
To prevent the broken logs, the class should allow multiple loggers as such:
class Foo {
private $loggers = array();
public function addLogger(Logger $logger) {
$this->loggers[] = $logger;
}
public function someMethod() {
foreach ($this->loggers as $logger) $logger->log('someMethod called');
}
}
Here encapsulation is maintained. It doesn't matter how many times addLogger() is called (even zero), it's not going to break anything anywhere.
Allowing objects to use different services at runtime
Sometimes is it useful to be able to substitute the services being used by an object at runtime. In these cases you should pass the dependency into the method that requires them:
class Foo {
public function someMethod(Dependency $dep) {
$baz = $dep->xx();
}
}
class Bar {
private $foo;
public function __construct(Foo $foo, Dependency $dep) {
$this->foo = $foo;
$this->foo->someMethod($dep);
}
}
$foo = new Foo();
$bar = new Bar($foo, new Dependency);
Here, the dependency is passed in as an argument to the method(s) that require it as a collaborator. Because the dependency is not encapsulated within the $foo object, it's impossible to cause any unexpected knock-on effects by using a different Dependency object.
However, be aware that doing this can be detrimental as it creates extra dependencies higher up the code. In the example above, the Bar class now has an extra Dependency to allow for this behaviour. I've written about this problem in detail here: OOP: The "courier" anti-pattern. As a general rule, constructor injection should be preferred to either passing in dependencies to methods or setter methods for dependencies.
Conclusion
Always favour Constructor Injection for dependencies which are needed for an object to successfully fulfil its contract and use either Setter Injection or Constructor Injection for dependencies which aren't required and won't cause unexpected errors if they're not supplied. Finally, whenever Setter Injection is used, the setter injection method should extend rather than replace functionality.