For a lot of people, approaching the Laravel framework for the first time there is one particular section of the docs that they often run up against a brick wall…the concept of service providers and the service container.
If you’ve found yourself re-reading the docs for the tenth time and still it doesn’t make sense, you’re not alone!
Many people breeze through the MVC concepts surrounding routing, controllers, templating — and even Eloquent models are relatively simple at their core. However, the service container seems less tangible and less easy to understand.
Quite often, this is the result of a limited knowledge surrounding the concept of dependency injection.
So, let’s rewind for a moment and remind ourselves what is dependency injection and why is the service container important…after all, if we didn’t care about dependency injection, we wouldn’t need a container.
If you have spent any time in object-oriented programming (OOP), you may be familiar that it is a good idea to separate your code into different classes. This allows us to follow the single-responsibility principle…different classes for different things.
However, as soon as your code needs to actually use another class this is known as a “dependency”.
You may be tempted to simply spin-up a new instance of the class you want to use and carry on your merry way:
class ExampleA
{
public function runSomething()
{
//incorrect
$exampleB = new ExampleB();
$exampleB->runSomethingElse();
}
}
But beware! Whenever we use the new
keyword we are tight-coupling our two classes. Now we no longer have our separation of concerns — despite the code being in two different files, we cannot be sure that changes from one class will not disrupt the next.
So how do we solve this problem and decouple our classes? This is where dependency injection comes in. Instead of creating a new instance of our dependency directly, we can get this instance from somewhere else:
class ExampleA
{
private $exampleB; public function __construct($exampleB)
{
$this->exampleB = $exampleB;
} public function runSomething()
{
//correct
$this->exampleB->runSomethingElse();
}
}
The example above is implementing a type of dependency injection called constructor injection. That is because all the dependencies are passed to the class via it’s __construct()
method and is a common pattern you will see.
But you may still be asking — what is this magical place we are calling “somewhere else” that is supplying all these dependencies to our class?
And the answer is…the service container (also known as dependency container and IoC container).
Now we know where the service container fits in, the final piece of the puzzle is how to use it. So firstly, our container needs to know what dependencies go with what classes — these are called “bindings”. We can setup bindings manually in the boot()
method of our Laravel service providers.
These are effectively instructions telling our service container that when we ask for a particular class name, we want it to resolve to a specific object. This means the control is now with our container, not hard-baked into our classes.
Having to bind each dependency manually though is a laborious task. Luckily, the Laravel service container offers a feature called auto-wiring which does all the hard work for us.
Do you remember our constructor injection example above…well to take advantage of auto-wiring all we need to do is type-hint each of our dependencies in the __construct()
method, and Laravel will automatically resolve it from the service container:
class ExampleA
{
public function __construct(private ExampleB $exampleB)
{
}
}
Because of this you will often find that you rarely have to bind your dependencies manually, even in a large project. On top of which Laravel has a whole load of built-in bindings that you can harness to keep your code light.
And there we are…the service container is simply an abstraction layer that means we don’t have to tightly-couple our dependencies and controls what is resolved whenever areas of our app need to use a particular class.
Credit — my own understanding has been helpfully influenced by this great talk by Kai Sassnowski https://www.youtube.com/watch?v=y7EbrV4ChJs