Dependency is an object, a class needs to function. For example, if your class needs to log something using a logger object, that means it has a dependency on that object. Now, let’s see what Dependency Injection is.
Many years ago, Martin Fowler wrote an article¹ that introduced the term Dependency Injection
. It’s a technique, in which dependencies are injected (pushed) into objects, instead of created inside it. If you want to follow that technique, you’re not allowed to use new
or static methods to instantiate a class. Should you care about it? Absolutely! Read on, to find out the reasons.
Advantages
- Less coupling between a class and its dependencies
- No hidden dependencies
- More reusable code
- Easier unit testing
- Less maintenance nightmare
- Easier configuration using external files
- Easier implementation switch, using interfaces²
Disadvantages
- The consumer must know how to properly construct the object they want to use
- Sometimes, it’s not easy to understand where the dependencies come from, especially when not using an IDE
Example
Let’s find out what issues the following piece of code has and how it can be improved using Dependency Injection.
class A
{
private $logger;
public function __construct()
{
$this->logger = new FileLogger('/var/log/a.log');
}
public function doSomething()
{
// do something
$this->logger->log('did something');
}
}
Issues
The class A
has a hidden dependency on the class FileLogger
. The problem is that you can’t change its instance with another one, without modifying the source. It’s also difficult to reuse and test it, because you must be aware of it. It’s called hidden, because it’s not part of the signature of the constructor nor of the doSomething()
or any setter method. The only way you can discover that dependency is by reading the implementation of the class.
Fortunately, you can solve those issues by using one of the following dependency injection types.
Constructor Injection
All required dependencies must be passed into the class at the instantiation time. This is good, because the consumer knows exactly what dependencies they need to use the class.
class A
{
private $logger;
public function __construct(FileLogger $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
// do something
$this->logger->log('did something');
}
}
$logger = new FileLogger('/var/log/a.log');
$a = new A($logger);
$a->doSomething();
Setter Injection
Dependencies have to be passed into the class using setter methods. It’s used for optional dependencies, but it’s not a very good solution when a class uses many of them, since it’s not obvious, at least easily, what methods the consumer must call in order to have a fully workable object.
class A
{
private $logger;
public function setLogger(FileLogger $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
// do something
$this->logger->log('did something');
}
}
$logger = new FileLogger('/var/log/a.log');
$a = new A;
$a->setLogger($logger);
$a->doSomething();
Interface Injection
First, you have to define an interface, which will be used for injecting the dependency.
interface InjectLoggerInterface
{
public function setLogger(FileLogger $logger);
}
Then, you should implement that interface whenever you want to use the dependency.
class A implements InjectLoggerInterface
{
private $logger;
public function setLogger(FileLogger $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
// do something
$this->logger->log('did something');
}
}
$logger = new FileLogger('/var/log/a.log');
$a = new A;
$a->setLogger($logger);
$a->doSomething();
Which type should you use?
Dependencies are separated in two categories, hard, when the class can’t be functional without them and soft, when it can. If you must inject a hard dependency use the constructor injection, otherwise use the setter one.
¹ Martin Fowler’s original article about Dependency Injection
² You should always program to an interface, not an implementation. That means I should have used a LoggerInterface
as method parameter, instead of the FileLogger
class. That would allow me to use a different type of logger, such as DbLogger
, SysLogger
, etc, without modifying the class A
.