Refactor towards dependency injection

A lot of senior developers are advocating to inject your dependencies. However, in many cases you have to deal with legacy code, an application already in production or you are half way the development of a new application. So although it can be a good advice, it might not be directly applicable for your use case if you have done things differently.

This guide will help with refactoring your code base so you can apply dependency injection as well. In five simple steps and two distinct changes in your code the pattern is implemented.

In this post I will give a step-by-step understanding of the process. My examples will focus on php and use some external libraries like Twig and Swift, but all these principles are viable for any type of an (object oriented) code base.

Step 1: locate dependencies

It is important to know you have dependencies. So scan the class and locate all new keywords. Also mind the singletons used, which you can recognize by methods like ClassName::newInstance() or ClassName::getSingleton().

Below is a MyService class which acts as an example during the guide. In this class, there are some dependencies using both the new keywords as there are dependencies created by the singleton pattern.


<?php
class MyService
{
    public function sendRegistration(User $user)
    {
        // Create html for the message
        $path   = '/path/to/templates';
        $loader = new Twig_Loader_Filesystem($path);
        $twig   = new Twig_Environment($loader);
        $html   = $twig->render('welcome.html', array(
            'user' => $user
        ));

        // Create email message
        $from = 'jurian@juriansluiman.nl';
        $to   = array(
            $user->getEmail() => $user->getName()
        );

        $message = Swift_Message::newInstance();
        $message->setSubject('Registration confirmation')
                ->setFrom($from)
                ->setTo($to)
                ->setBody($html);

        // Create message transport
        $host = 'smtp.example.org';
        $transport = Swift_SmtpTransport::newInstance();
        $transport->setHost($host)
                  ->setUsername('your username')
                  ->setPassword('your password');
        $mailer = Swift_Mailer::newInstance($transport);

        // Send the message
        return $mailer->send($message);
    }
}
?>

In the above situation, there is a dependency on two Twig_ classes and three Switft_ classes. In total, this class has five dependencies.

Step 2: create getters

The next step is to extract all new and getInstance() calls in this method and put them in separate methods. The class will look something like this:


<?php
class MyService
{
    public function sendRegistration(User $user)
    {
        // Create html for the message
        $twig = $this->getRenderer();
        $html = $twig->render('welcome.html', array(
            'user' => $user
        ));

        // Create email message
        $from = 'jurian@juriansluiman.nl';
        $to   = array(
            $user->getEmail() => $user->getName()
        );

        $message = $this->getMessage();
        $message->setSubject('Registration confirmation')
                ->setFrom($from)
                ->setTo($to)
                ->setBody($html);

        // Create message transport
        $mailer = $this->getMailer();

        // Send the message
        return $mailer->send($message);
    }

    protected function getRenderer()
    {
        $path   = '/path/to/templates';
        $loader = new Twig_Loader_Filesystem($path);
        return new Twig_Environment($loader);
    }

    protected function getMessage()
    {
        return Swift_Message::newInstance();
    }

    protected function getMailer()
    {
        $host = 'smtp.example.org';
        $transport = Swift_SmtpTransport::newInstance();
        $transport->setHost($host)
                  ->setUsername('your username')
                  ->setPassword('your password');
        return Swift_Mailer::newInstance($transport);
    }
}
?>

As you see I extracted all direct dependencies for the sendRegistration() method. There is no need to have access to the Twig loader or the Swift transport, so it is possible to return directly the Twig environment (or as I called it, "renderer") and the mailer.

Step 3: identify hard dependencies

There are two types of dependencies: hard ones and soft ones. See it like hard dependencies are really required to fulfil the task. Soft ones are just cool to have. In the case of the mailer, the renderer, message and mailer class are all required to send the registration email.

So here, three hard dependencies are identified:

  1. Twig_Environment as a renderer
  2. Swift_Message as the email message
  3. Swift_Mailer as the service to send the message

Step 4: create a constructor

The constructor follows a simple rule: inject all objects which are a hard dependency. In code, the class will get a constructor:


<?php
class MyService
{
    protected $renderer;
    protected $mailer;
    protected $message;

    public function __construct(
        Twig_Environment $renderer,
        Swift_Mailer $mailer,
        Swift_Message $message,
    )
    {
        $this->renderer = $renderer;
        $this->mailer   = $mailer;
        $this->message  = $message;
    }

    public function sendRegistration(User $user)
    {
        // Code like in previous example
    }

    protected function getRenderer()
    {
        return $this->renderer;
    }

    protected function getMailer()
    {
        return $this->mailer;
    }

    protected function getMessage()
    {
        return $this->message;
    }
}
?>

Step 5: review the code

If you compare the first example with the last example, there are a few things which should be clear now:

  1. It is directly visible what the class "needs". The service is requesting a renderer, mailer and message. Could you see that clearly in the first example without examining the whole code base?
  2. Configuration of the dependencies is outside the scope of the class. Should it matter if you send the email via SMTP or php's mail()? The class now accepts a configured dependency without the need to bother about the implementation details.
  3. Swapping implementation is easy. Like #2 here above, if you used the mail() function for development, but want to use a 3rd party email service provider, you can do so! This isn't a hard job anymore, because all these details are extracted.
  4. If you want to test the function, you will do that with dummy objects called stubs or mocks. Because it is very easy to swap the implementation, you can inject a class that looks like a Swift_Mailer but is not a mailer at all!

Step 6: use a container

With the five steps completed, I hope you understand how you can refactor towards objects where all dependencies are injected. However, it can be quite difficult to create the MyService class. Everywhere you need this class, you have to configure the renderer, mailer and message!

There is a simple solution for it, called a dependency injection container. How to use a container, is something I will keep for another blog post.