Dependency Injection

Now that we see how the container works, let’s see how we can use it to implement Dependency Injection in Laravel.

What is Dependency Injection?

Dependency Injection is the act of adding (injecting) any dependencies into a class, rather than instantiating them somewhere within the class code itself. Often, dependencies are defined as type-hinted parameters of a constructor method.

Take this constructor method, for example:

1 public function __construct(HelloWorld $greeter)
2 {
3     $this->greeter = $greeter;
4 }

By type-hinting HelloWorld as a parameter, we’re explicitly stating that an instance of HelloWorld is a class dependency.

This is opposite of direct instantiation:

1 public function __construct()
2 {
3     $this->greeter = new HelloWorld;
4 }

If you find yourself asking why Dependency Injection is used, this Stack Overflow answer is a great place to start. I’ll cover some benefits of it in the following examples.

Next, we’ll see an example of Dependency Injection in action, using Laravel’s IoC container.

Adding Controller Dependencies

This is a very common use case within Laravel.

Normally, if we set a controller to expect a class in its constructor method, we also need to add those dependencies when the class is created. However, what happens when you define a dependency on a Laravel controller? We would need to instantiate the controller somewhere ourselves:

1 $crtl = new ContainerController( new HelloWorld );

That’s great, but we don’t directly instantiate a controller within Laravel - the router handles it for us.

We can still, however, inject controller dependencies with the use of Laravel’s IoC container!

Keeping the same GreetableInterface and HelloWorld classes from before, let’s now imagine we bind our /container route to a controller:

File: app/routes.php


 1 interface GreetableInterface {
 2 
 3     public function greet();
 4 
 5 }
 6 
 7 class HelloWorld implements GreetableInterface {
 8 
 9     public function greet()
10     {
11         return 'Hello, World!';
12     }
13 }
14 
15 Route::get('/container', 'ContainerController@container');

Now in our new controller, we can set HelloWorld as a parameter in the constructor method:

File: app/controllers/ContainerController.php


 1 <?php
 2 
 3 class ContainerController extends BaseController {
 4 
 5     protected $greeter;
 6 
 7     // Class dependency: HelloWorld
 8     public function __construct(HelloWorld $greeter)
 9     {
10         $this->greeter = $greeter;
11     }
12 
13     public function container()
14     {
15         return $this->greeter->greet();
16     }
17 
18 }

Now head to your /container route and you should, once again, see:

1 Hello, World!

Note, however, that we did NOT bind anything to the container. It simply “just worked” - an instance of HelloWorld was passed to the controller!

This is because the IoC container will automatically attempt to resolve any dependency set in the constructor method of a controller. Laravel will inject specified dependencies for us!

Interfaces as Dependencies

We’re not done, however. Here is what we’re building up to!

What if, instead of specifying HelloWorld as the controller’s dependency, we specified the interface GreetableInterface?

Let’s see what that would look like:

File: app/controllers/ContainerController.php


 1 <?php
 2 
 3 class ContainerController extends BaseController {
 4 
 5     protected $greeter;
 6 
 7     // Class dependency: GreetableInterface
 8     public function __construct(GreetableInterface $greeter)
 9     {
10         $this->greeter = $greeter;
11     }
12 
13     public function container()
14     {
15         echo $this->greeter->greet();
16     }
17 
18 }

If we try to run this as-is, we’ll get an error:

1 Illuminate\Container\BindingResolutionException: 
2 Target [GreetableInterface] is not instantiable

The class GreetableInterface is of course not instantiable, as it is an interface. We can see, however, that Laravel is attempting to instantiate it in order to resolve the class dependency.

Let’s fix that - when the container sees that our controller depends on an instance of GreetableInterface, we’ll use the container’s bind() method to tell Laravel to give the controller and instance of HelloWorld:

File: app/routes.php


 1 interface GreetableInterface {
 2 
 3     public function greet();
 4 
 5 }
 6 
 7 class HelloWorld implements GreetableInterface {
 8 
 9     public function greet()
10     {
11         return 'Hello, World!';
12     }
13 }
14 
15 // Binding HelloWorld when asked for
16 // GreetableInterface here!!
17 App::bind('GreetableInterface', 'HelloWorld');
18 
19 Route::get('/container', 'ContainerController@container');

Now re-run your /container route, you’ll see Hello, World! once again!

Note that I didn’t use a closure to bind HelloWorld - You can simply pass the concrete class name as a string if you wish. A closure is useful when your implementation has its own dependencies that need to be passed into its constructor method.

Why Dependency Injection?

Why would we want to specify an interface as a dependency instead of a concrete class?

We want to because we need any class dependency given to the constructor to be a subclass of an interface. In this way, we can safely use any implementation - the method we need will always be available.

Put succinctly, we can change the implementation at will, without effecting other portions of our application code.

Here’s an example. It’s something I’ve had to do many times in real applications.

Don’t copy and paste this example. I’m omitting some details, such as using configuration variables for API keys, to clarify the point.

Let’s say our application sends emails using Amazon’s AWS. To accomplish this, we have defined an Emailer interface and an implementing class AwsEmailer:

 1 interface Emailer {
 2 
 3     public function send($to, $from, $subject, $message);
 4 }
 5 
 6 class AwsEmailer implements Emailer {
 7 
 8     protected $aws;
 9 
10     public function __construct(AwsSDK $aws)
11     {
12         $this->aws = $aws;
13     }
14 
15     public function send($to, $from, $subject, $message)
16     {
17         $this->aws->addTo($to)
18             ->setFrom($from)
19             ->setSubject($subject)
20             ->setMessage($message);
21             ->sendEmail();
22     }
23 }

We bind Emailer to the AwsEmailer implementation:

1 App::bind('Emailer', function()
2 {
3     return new AwsEmailer( new AwsSDK );
4 });

A controller uses the Emailer interface as a dependency:

File: app/controllers/EmailController.php


 1 class EmailController extends BaseController {
 2 
 3     protected $emailer;
 4 
 5     // Class dependency: Emailer
 6     public function __construct(Emailer $emailer)
 7     {
 8         $this->emailer = $emailer;
 9     }
10 
11     public function email()
12     {
13         $this->emailer->send(
14             'ex-to@example.com',
15             'ex-from@example.com',
16             'Peanut Butter Jelly Time!',
17             "It's that time again! And so on!"
18         );
19 
20         return Redirect::to('/');
21     }
22 
23 }

Let’s further pretend that someday down the line, our application grows in scope and needs some more functionality than AWS provides. After some searching and weighing of options, you decide on SendGrid.

How do you then proceed to change your application over to SendGrid? Because we used interfaces and Laravel’s IoC container, switching to SendGrid is easy!

First, make an implementation of Emailer which uses SendGrid!

 1 class SendGridEmailer implements Emailer {
 2 
 3     protected $sendgrid;
 4 
 5     public function __construct(SendGridSDK $sendgrid)
 6     {
 7         $this->sendgrid = $sendgrid;
 8     }
 9 
10     public function send($to, $from, $subject, $message)
11     {
12         $mail = $this->sendgrid->mail->instance();
13 
14         $mail->addTo($to)
15             ->setFrom($from)
16             ->setSubject($subject)
17             ->setText( strip_tags($message) )
18             ->setHtml($message)
19             ->send();
20 
21         $this->sendgrid->web->send($mail);
22     }
23 }

Next, (and lastly!), set the application to use SendGrid rather than Aws. Because we have our call to bind() in the IoC container, changing the implementation of Emailer from AwsEmailer to SendGridEmailer is as simple as this one change:

 1 // From	
 2 App::bind('Emailer', function()
 3 {
 4     return new AwsEmailer( new AwsSDK );
 5 });
 6 
 7 // To
 8 App::bind('Emailer', function()
 9 {
10     return new SendGridEmailer( new SendGridSDK );
11 });

Note that we did this all without changing a line of code elsewhere in our application. Enforcing the use of the interface Emailer as a dependency guarantees that any class injected will have the send() method available.

We can see this in our example. The controller still called $this->emailer->send() without having to be modified when we switched from AwsEmailer to SendGridEmailer implementations.

Wrapping Up

Dependency Injection and Inversion of Control are patterns used over and over again in Laravel development.

As you’ll see, we’ll define a lot of interfaces in order to make our code more maintainable, and help in testing. Laravel’s IoC container makes this easy for us.