At almost every Laravel conference you can find at least one talk that covers Laravel's service container – and rightfully so.
The service container is one of the most important pieces of the framework. It is responsible for managing your class dependencies and allows you to perform dependency injection.
Why should you use dependency injection?
If you are new to Laravel / modern PHP frameworks you might wonder what "dependency injection" actually means, so let me give you an example.
In your PHP classes, no matter if its a controller, a "service" class, or anything else, you will at some point need to include "dependencies".
Lets say that your application has a GoogleMaps
class, which needs some API credentials and you want to use that class in one of your controllers:
class GoogleMaps
{
// ...
}
class LocationController
{
public function index(Request $request)
{
$maps = new GoogleMaps();
$coordinates = $maps->getCoordinates($request->get('address'));
}
}
As you can see, inside the index
method, we created a new instance of the GoogleMaps
class.
So what's the problem with this, you might ask. Well, what if you want to use the same GoogleMaps class in another part of your application? Or maybe the GoogleMaps class has additional dependencies that you would need to pass.
This quickly gets cumbersome to manage. Because of this, we have the concept of dependency injection.
In fact, in the code snippet above, we already use dependency injection.
We inject the Request
class into our controller method, without creating a new instance of it.
Automatic dependency injection
By default, Laravel allows you to use a completely configuration-free dependency injection system. All you need to do is type hint the class that you want to inject – for example in your controller, middleware, jobs, event listeners, etc.
The example above would become:
class LocationController
{
public function index(Request $request, GoogleMaps $maps)
{
$coordinates = $maps->getCoordinates($request->get('address'));
}
}
By simply type-hinting the GoogleMaps class in our controller method, Laravel is automatically looking up the GoogleMaps
class and its dependencies, tries to create a new instance of the class and pass it to the controller method.
In a controller, you might also need a dependency throughout all of the controller methods. In this case, you can simply inject the class in the controllers' constructor:
class LocationController
{
protected GoogleMaps $maps;
public function __construct(GoogleMaps $maps)
{
$this->maps = $maps;
}
public function index(Request $request)
{
$coordinates = $this->maps->getCoordinates($request->get('address'));
}
}
This zero-configuration dependency injection is amazing and you will most likely not need to manually configure dependency injection. There are, however, certain situations where you might want to handle dependency injection manually. Let's take a look at some examples.
Manual binding
Imagine that our GoogleMaps
class requires some API credentials in its constructor, like this:
class GoogleMaps
{
public function __construct(protected $apiKey) {}
}
When you now try to inject this class in your controllers, events, etc. Laravel will not know what to do with the $apiKey
constructor argument and throw an exception, as its unable to create a new instance of the class for you:
'Unresolvable dependency resolving [Parameter #0 [
$apiKey ]] in class GoogleMaps'
So let's tell Laravel how this class can be resolved.
Typically, you will register all manual dependencies in a service provider. As the name suggests, this should take place in the register
method.
Each service provider has the container available as the app
property.
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(GoogleMaps::class, function($app) {
return new GoogleMaps(config('services.google.key'));
});
}
}
Using the bind
method on the app/container, we have now told Laravel that when it should try to resolve the GoogleMaps
class, it should create a new instance of the class using a key from our configuration file.
Resolving additional dependencies
When manually binding a class, you might still want to make use of the automatic dependency resolution. For example, our GoogleMaps class could have a second constructor dependency that we want to be resolved from the container automatically instead.
In this case, we can make use of the $app
variable that is automatically available when binding something to the container:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(GoogleMaps::class, function($app) {
return new GoogleMaps(config('services.google.key'), $app->make(CoordinateParser::class));
});
}
}
As you might have guessed already, the $app->make
method can be used to resolve anything from the service container manually.
Binding singletons
Using the bind
method will register a regular binding. This means that, every time this dependency will be resolved within one request/response lifecycle, Laravel is going to create a new object.
In some situations you do not want this behavior, but you rather want to reuse the same object at various different places.
This is called a singleton
and can be registered using the singleton
method:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(GoogleMaps::class, function($app) {
return new GoogleMaps(config('services.google.key'));
});
}
}
Now resolving the GoogleMaps
dependency twice, will return the same object.
Binding existing instances
Instead of instructing Laravel how to build a certain dependency when it is required, you can also bind an actual object instance in the service container. Just like the singleton
above, Laravel will always return this one instance when you resolve the dependency:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$maps = new GoogleMaps(config('services.google.key'));
$this->app->instance(GoogleMaps::class, $maps);
}
}
Binding primitive values
Laravel's service container does not only allow you to bind classes, but you can also bind primitive values. For example, imagine that one of your controllers requires a value from a config file:
class UserReportController
{
public function __construct(protected $cacheTtl) {}
}
To bind this, we need to make use of "contextual binding". This tells the service container when to bind which value to a certain class.
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(UserReportController::class)
->needs('$cacheTtl')
->give(now()->addDay());
}
}
A lot of times, these primitive values might already be available in one of your configuration files. To make your life easier, Laravel has a convenient helper method that allows you to pass a configuration value directly:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(UserReportController::class)
->needs('$cacheTtl')
->giveConfig('cache.ttl');
}
}