01. Understanding Laravel - The Service Container

01. Understanding Laravel - The Service Container

At the core of the Laravel framework, is the service container. The service container implements one of the several design patterns called Dependency Injection which is a more specific form of Inversion of Control.

I am going to try and explain what an application without Dependency Injection  looks like so that you can at least appreciate why we need Dependency Injection in the first place. Dependency Injection is actually something that we humans implement very well in our everyday lives. Let’s say that you need to send a message to your friend that lives in a different continent. Currently: using the tech available now, what you would do is to use an app, most probably on your phone, to send the message to your lovely friend and that’s it.

Sending message without dependency injection.

Without Dependency Injection,  however, you would need to know how to code an application that can send messages over the wire, the specifics of networking like the TCP/IP network layers, and how to implement each layer across an array of different applications. This means you would need the programming know-how to create an application like Gmail, how to create an operating system that your Gmail app is going to be running on and how to create device drivers that your operating system is going to use to control the hardware. Not only that, you will also need to know how the hardware parts: the battery, the screen, the network interface card etc, are made so that you can create and assemble them to create the phone that you will use to send the message. As you can imagine this is a very strenuous and complicated way of doing things, when all you want is to send a message, I bet if you were to go to school to learn all these technologies you would be dead by the time you’re able to send a message to your friend.

So in programming terms if a method (client) is to be implemented without Dependency Injection it would need to know about the entire dependency graph, how to create each node in the graph and how to link them together before it can call the methods of it’s main dependency (the service). In a large software, dependencies will likely have their own dependencies which in turn have their own dependencies, creating a very complicated and confusing graph. The client will then have to know all about these classes and how to construct the graph properly before it can call methods on it’s main dependency.

This is a direct violation of the Single Responsibility Principle, which implies that software modules should only have one reason to change  — Robert C Martin. In this case if an object in the dependency graph changes, say by removing or adding a constructor argument during refactoring then you would also have to change the client code where that object is instantiated. If you find that you have to update your method or class for many different reasons then you may be violating the Single Responsibility Principle.

Send message with dependency injection

With Dependency Injection however you will only have to inject an instance of a dependency to the client method, maybe as a method argument or a property of the class that is set during object initialization or through a setter method. This is similar to you walking into a shop and buying a phone that has Gmail already installed, is charged up and connected to your Internet Service Provider, so all you have to do is open Gmail, type in the message, your friend’s email address and hit send.


Using this pattern though another part of your application will have to be responsible for instantiating the dependencies that you need. In Laravel this responsibility  is fulfilled by service providers. When a service provider creates an instance of a class (a service), it puts that object into the service container. Then you’ll be able to inject instances of these services wherever you want to use them in your application. For now you can think of the service container as the shop you walk into to buy a phone, or as a cars’ mechanic toolbox. The toolbox contains all the tools needed by the mechanic when fixing a car. The service container contains all the objects that you may depend on when solving problems within your code.

So if you need to send an email for example, the MailServiceProvider will put an instance of the Mailer into the service container. When you need to send an email anywhere in your application, all you have to do is inject an instance of the Mailer class into a method and call the Mailer’s send method. The Laravel framework will know how to get / create an instance of the Mailer from the container, all you have to be concerned about in your client is sending email, not how the Mailer is instantiated or how it works behind the scene.

Talk is cheap, show me the code. ― Linus Torvalds.

The Service Providers

There is a lot you can do within Laravel’s Service Providers, one of this is being able to add things into the service container. Laravel makes it very easy to create service providers using the make:provider artisan command

php artisan make:provider MailServiceProvider

This will create a class: MailServiceProvider, with a register() method which we can use to bind/add things into the service container. The bind method of the service container, accepts a class or interface name that we wish to register along with a Closure that returns an instance of the class. Using this we can bind instances of interfaces, concretions, or simple variables into the container. Below, we have added an instance of the Mailer class to the service container.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class MailServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            SmtpTransport::class, 
            function ($app) {
                return new SmtpTransport(
                    $smtpHost, $smtpPort, $username, $password);
        });
        
        $this->app->bind(Mailer::class, function ($app) {
            return new Swift_Mailer($app->make(SmtpTransport::class));
        });
    }
}
app/Providers/MailServiceProvider.php

Injecting Dependencies

The Laravel framework is pretty clever in that it will automatically resolve instances from the container if you just type hint the class names inside your controller methods, your event handlers or other classes that use services in the container.

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class SendEmailController extends Controller
{
    /**
     * The  mailer implementation
     *
     * @var Mailer
     */
    protected $mailer;

    /**
     * Create a new controller instance.
     *
     * @param  Mailer $mailer
     * @return void
     */
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * Send an email to a user
     *
     * @return void
     */
    public function __invoke()
    {
        $message = (new Email('Subject'))->to($address)->body($msg);
        $this->mailer->send($message);
    }
}
app/Http/Controllers/SendEmailController.php

There is a number of ways that you can use to add things to the service container, like singletons for example, and a number of ways you can use to resolve things out of the service container. For that I do encourage you to learn more by reading the official Laravel docs.

Conclusion

Often times though, when you are just getting started with Laravel you will not be writing a lot of service providers. This is mostly because most of the functionality has already been implemented by Laravel itself, or if your are going to use a 3rd party library it will usually have it’s own service providers that inject instances that you will be using in your Laravel applications. But as your application starts to grow you may find you have to override the default Laravel implementation of a certain functionality or that you need to add functionality to Laravel in a decoupled way, or write your own packages that integrate easily with Laravel, whatever the case a deeper understanding of the service container and service providers will prove to be very useful.


This post is part of the ongoing understanding laravel series. In the next post we discuss laravel facades.