by

Dependency Injection explained in plain English

Dependency Injection (DI) is a simple concept that’s often explained in complicated terms. It’s not rocket surgery, just giving objects to other objects!

An example of DI at its simplest:

// Instantiate a dependency, assume the class is defined elsewhere
$mailer = new Mailer( /* SMTP details */ );

// Your code
function email($mailer) {
    $mailer->send("Hello, World!");
}

// Calling function which injects the dependency
email($mailer);

This article provides examples in PHP but the principles can be applied to any imperative language

Dependency Injection is basically a way of handling a situation where a class need to make use of another class.

Is that it?!

Yep. DI is just injecting dependencies as parameters (or via getter methods, a la Symfony), although in practice they’re more likely to be in class constructor or getter/setter methods.

For example, a mailer class may want to do logging, so you pass an instance of your logger to the mailer’s constructor:

// The implementation of how SMTP is handled
// is irrelevant and is therefore assumed
class Mailer {

    // Logger instance
    private $logger;

    // Store the injected dependency
    public __construct(Logger $logger) {
        $this->logger = $logger;
    }

    // Send an email and log the result
    public function email($body) {

        /* Assume this function exists and grab the result */
        $result = $this->send($body);

        // Log the result
        $this->logger->log($result);

    }

}

// Again, the implementation is irrelevant.
// Just note that it is a basic logger
class Logger {
    private $logFile;
    public function __construct($logFile) { $this->logFile = $logFile; }
    public function log($message) {
        file_put_contents($this->logFile, $message.PHP_EOL, FILE_APPEND);
    }
}

// Instantiate the dependency
$logger = new Logger('log.txt');

// Instantiate the dependant
$mailer = new Mailer($logger);

// Do your thing!
$mailer->email('Hello, World!');

All we’re doing here is expressing the same principle at an object- rather than function-level. The Mailer needs a Logger, so we simply give it one!

Type Hinting

Note that the mailer’s constructor is type hinted like so: public __construct(Logger $logger). This means that it will accept any object of the type Logger so we can swap-out the dependency for another one if we choose, provided the interface is the same. i.e. if we find another logger class with the same functions, parameters and return types, we can use that instead.

Type Hinting is not a requirement for DI, but the two concepts work well together. For example, PSR3 defines the interface for a logger, meaning any PSR3-compliant logger can be used interchangeable as desired. I won’t go into this too much, suffice it to say that classes that extend an interface can be type-hinted to the interface rather than the class, which simply means that you could rewrite the constructor in the mailer example above as public __construct(Psr\Log\LoggerInterface $logger) and use any PSR3-compliant logger you like!

Comparison with tightly-coupled dependencies

Remember when I said that Dependency Injection is basically a way of handling a situation where a class need to make use of another class? Another way of handling this situation is by tightly-coupled dependencies and, if you’re reading this, is probably what you’re doing right now:

// Assume the `send` function is defined
class Mailer {
    public function email($message) {
        $result = $this->send($message);
        $logger = new Logger;
        $logger->log($result);
    }
}

It’s this line: $logger = new Logger; which demonstrates the key difference. Instead of injecting a dependency we’re explicitly defining it in the dependant class, making it more difficult to swap-out.

To say this is a bad thing in itself would be short-sighted (see critique below) suffice it to say that some may frown on this perfectly-valid approach.

Service Containers & Autoloading

If you’re a PHP developer you may have heard the term “Dependency Injection” in the context of the Laravel framework as a feature of its Service Container (SC). How these systems work is outside the scope of this article, but Laravel’s Service Container is basically a DI-machine that uses reflection to determine which object is required, instantiate one and inject it into your code. It’s a pretty snazzy feature and the Symfony framework has its own way of handing DI.

For further reading, I’d recommend Laravels’ Service Container and Symfony’s Service Container and DependencyInjection Component.

Just remember that all we’re really doing is passing one object to another!

Critique of DI

In Dependency Injection is EVIL, it is argued that DI is far from best practice and is simply one possible way of approaching a problem. I won’t attempt to summarise this behemoth of an article and will simply quote a few pertinent points:

[A copy program] is used to copy a file from one device to another and where the file can exist on any number of different devices. Instead of having the choice of devices dealt with internally by some sort of switch statement, which would require that the program had prior knowledge of each possible device, the choice of device is made externally and then injected into the program. This allows new device objects to be created without having to amend the program.

Dependency Injection is only a good idea when a consuming object has a dependency which can be switched at runtime between a number of alternatives, and where the choice of which alternative to use can be made outside of the consuming object and then injected into it.

While DI has benefits in some circumstances…I do not believe in the idea that it automatically provides benefits in all circumstances [and] the application of DI in the wrong circumstances should be considered as being evil and not a universal panacea

Caveat emptor!

Add Comment

Comment