Laravel IoC Container and Automatic Dependency Injection

Monday, June 05, 2017

Laravel IoC Container is a powerful component for inserting class dependencies at runtime. It is the core of the Laravel Framework. Understanding the IoC Container helps us write better application using the Framework and also to extend the Framework. Let’s explore how the Laravel IoC Container works.

1. Class Dependency Principles

1.1 Dependency Injection

So you have probably heard of this term Dependency Injection at least once while learning Laravel. DI short for Dependency Injection, is when we pass dependencies of a class via its constructor while instantiating a new instance of that class or via setter methods of that class.

Let’s look at an example to understand it clearly.

class EmailNotifier
{
    public function notify()
    {
        echo 'Sending payment notification via email' . PHP_EOL;
    }
}

class Billing
{
    protected $notifier;

    public function __construct()
    {
        // HardCoded Notification Implementation inside the class
        $this->notifier = new EmailNotifier;
    }

    public function pay()
    {
        // Process the bill payment
        // ......
        $this->notifier->notify();
    }
}

In the above implementation of Billing class, the EmailNotifier is instantiated inside the constructor. By doing this way, we are taking away the ability to swap the Notification functionality later. The Billing class knows too much about the Notification implementation. Let’s modify it.

class Billing 
{

    protected $notifier;

    // We now pass the implementation via the constructor.
    public function __construct(EmailNotifier $notifier)
    {
        $this->notifier = $notifier;
    }
    
    public function pay()
    {
        // Process the bill payment
        $this->notifier->notify();
    }

}

We have defined 2 classes, Billing and EmailNotifier. We can see here that the Billing class requires an instance of the EmailNotifier class to send the notification, which is the service provided by the EmailNotifier class.

# Billing depends on the EmailNotifier
$notifier = new EmailNotifier();

# Injecting the Dependecies for Billing
$biller = new Billing($notifier);
$biller->pay();

Output

Sending payment notification via email

As shown above, we inject the dependency of the Billing class by creating a new instance of EmailNotifier and passing it to the Billing constructor. This is what is called a dependency injection. Instead of creating a new instance of the EmailNotifier inside of the Billing constructor, we explicitly pass which notifier instance to use inside the Billing class.

1.2 Dependency Inversion

Dependency Inversion works with Dependency Injection. When specifying dependencies for a class, instead of specifying the concrete implementation of a dependency class, we must specify an Abstract Interface(i.e the interface of the class) of how the dependency class must be rather than the final implementation. This can be understood easily with an example.

We saw in the previous example that the Billing class depends on the EmailNotifier class for sending Notification. Now, the Billing class does not need to know that it needs to send notification via email. It just needs to know that it must send a Notification when a payment occurs. The Billing class knows too much about the implementation of Notification, but it must know less about the implementation of Notification. If we need to swap an alternate Notification Implementation, other than email(like SMS or Messenger Notification), we must be able to easily change the dependency. The dependency must be a high level representation of the implementation instead of an low level implementation of the functionalities.

Let’s define a interface for a Notification class.

interface Notification 
{
    public function notify();
}

Now, instead of type hinting the direct implementation of the Notifier, we type hint the Interface. This is the “high level” representation of the Notification implementation. This way, we can pass any Notification implementation until it implements the Notification interface.

class Billing
{
    protected $notifier;

    public function __construct(Notification $notifier)
    {
        $this->notifier = $notifier;
    }

    public function pay()
    {
        // Process bill payment

        $this->notifier->notify();
    }
}

Once the interface has been defined, we need to create Notification classes that implement the methods imposed by the Notification interface. This is the “low level” implementation of the “high level” representation of the functionalities required(i.e the Notification interface).

class SMSNotifier implements Notification
{
 
    public function notify()
    {
        echo 'Sending payment notificatio via SMS' . PHP_EOL;
    }
 
}

class EmailNotifier implements Notification
{
 
    public function notify()
    {
        echo 'Sending payment notificatio via Email' . PHP_EOL;
    }
 
}

Now we can easily pass any kind of Notification implementation to the Billing class and it works as expected. You can also create new implementation and easily swap the dependency.

# Passing the implementation as a dependency
# SMS Notifier
$billOne = new Billing(new SMSNotifier);
$billOne->pay();

# Email Notifier
$billTwo = new Billing(new EmailNotifier);
$billTwo->pay();

Output

Sending payment notificatio via SMS
Sending payment notificatio via Email

2. Automatic Dependency Injection

Both Dependency Injection and Inversion are software principles which are mainly focused on injecting of dependencies of a class while instantiating them(i.e creating an object of the class). You need to pass those dependencies if you are to create a object of a class, if those dependencies depend on other classes, you should also instantiate them and it goes on until you have all the dependencies resolved.

In Laravel, those dependencies are created and resolved automatically using the Service Container or IoC Container. Most of the Laravel core components and application folder have Automatic Dependency Injection using the Service Container. Think of the Service Container as an Hash Table that maps the interface(high level) to an concrete implementation(low level).

How does the Service Container knows how to resolve a dependency to an implementation ? Using the Service Provider. We won’t go into much detail about Service Provider, but it’s basically where we tell the Service Container, which instance/object to use when an Interface is required. All of the Service Providers are listed(even the Laravel Core Services) in the config/app.php files providers array.

To know more what Service Provider is and how to create one read the Service Provider Documentation. Knowledge of Service Provider is necessary to extend the Laravel Framework itself and to register your own components.

3. The Laravel IoC Container

Laravel IoC Container is the component that handles the Automatic Dependency Injection and Resolving of Interface to Implementation. To understand the IoC Container, let’s look into the component separately. All the components of the Laravel Core is available separately under the illuminate namespace. View illuminate/container on github.

In a empty folder, require the illuminate/container package using composer.

$ composer require illuminate/container

Let’s define some Interface, Implementations and a Consumer that accepts these implementation as dependencies.

<?php

namespace LaravelContainer;

interface NotificationInterface
{

  public function notify();

}

The NotificationInterface is the high level representation of the functionalities needed. Let’s define some implementation of this interface.

<?php

namespace LaravelContainer;

require_once 'NotificationInterface.php';

use LaravelContainer\NotificationInterface;

class EmailNotifier implements NotificationInterface
{

    public function notify()
    {
        echo 'Notifying via email' . PHP_EOL;
    }

}

<?php

namespace LaravelContainer;

require_once 'NotificationInterface.php';

use LaravelContainer\NotificationInterface;

class SlackNotifier implements NotificationInterface
{

  public function notify()
  {
    echo 'Notifying via slack' . PHP_EOL;
  }

}

EmailNotifier and SlackNotifier are the low level implementations of the NotificationInterface.

<?php

namespace LaravelContainer;

require_once 'NotificationInterface.php';

use LaravelContainer\NotificationInterface;

class Billing
{
    protected $notifier;

    public function __construct(NotificationInterface $notifier)
    {
        $this->notifier = $notifier;
    }

    public function pay()
    {
        // Process payment
        echo 'Processing payment' . PHP_EOL;

        $this->notifier->notify();
    }

}

Here Billing class requires an implementation of a NotificationInterface that needs to send notification. We can pass in either the EmailNotifier or the SlackNotifier, but later if we need to change this, we must do it everywhere the Billing occurs in our application. This is where the IoC Container comes in. It resolves these dependencies automatically and later if we need to change the implementation, we just have to change it only in the Container and it will be reflected everywhere inside the application.

<?php

require_once __dir__ . '/vendor/autoload.php';
# High level implementation requirements
require_once 'NotificationInterface.php';

# Low level implementations
require_once 'SlackNotifier.php';
require_once 'EmailNotifier.php';

# The consumer of the implementation
require_once 'Billing.php';

use LaravelContainer\SlackNotifier;
use LaravelContainer\EmailNotifier;

# Create a new Container instance
$container = new \Illuminate\Container\Container();

# Bind the Low Level implementation to resolve when the NotificationInterface is required
$container->bind('LaravelContainer\NotificationInterface', function () {
  return new EmailNotifier();
});

# Resolve the dependencies for Billing
$bill = $container->make('LaravelContainer\Billing');
$bill->pay(); // .... Notifying via email

// Later if we need to swap the implementation, just swap it in the Container
$container->bind('LaravelContainer\NotificationInterface', function () {
  return new SlackNotifier();
});

# Resolve the dependencies for Billing
$bill = $container->make('LaravelContainer\Billing');
$bill->pay(); // .... Notifying via slack

Output

$ php index.php
Processing payment
Notifying via email
Processing payment
Notifying via slack

As you can see the above example, with the IoC Container, the dependencies are automatically resolved and we get an instance of the Billing class with the currently bound NotificationInterface implementation.

We can also see that it is easy to swap implementation later in the application by just changing it inside the IoC Container.

4. How does the IoC Container does this ?

The Laravel IoC Container automatically injects the dependencies of a class. How does it identify the dependencies of a class ? Well, it uses Reflection. Let’s explore this a little further. We will use our Billing class and try to identify its dependencies using Reflection.

<?php

require_once 'Billing.php';
require_once 'SlackNotifier.php';

$reflection = new ReflectionClass('LaravelContainer\Billing');

$reflectionMethod = $reflection->getMethods();

$construct = $reflectionMethod[0];

# The __construct method
print_r($construct);
# The parameter of the construct method
print_r($construct->getParameters());
# The type of paramter passed to constructor
print_r($construct->getParameters()[0]->getClass());

Output

$ php reflection.php
Billing __construct method:
ReflectionMethod Object
(
    [name] => __construct
    [class] => LaravelContainer\Billing
)
Parameters of the Billing __construct method:
Array
(
    [0] => ReflectionParameter Object
        (
            [name] => notifier
        )

)
Class of the Type Hint for the paramter in construct:
ReflectionClass Object
(
    [name] => LaravelContainer\NotificationInterface
)

As you see in the above example output, it is clear that the constructor of the Billing class uses an implementation of the LaravelContainer\NotificationInterface interface. Once we get this, the Container knows which implementation to resolve for this particular interface using the binding that we created and it passes that instance to the Billing class.