This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the architecture category.
Last Updated: 2024-12-03
Imagine Application
needs Foo
which needs Bar
which needs Bim
.
Application
creates Foo
, Foo
creates Bar
, Bar
creates Bim
etc.
Application
creates everything. I.e. it creates Bim
, Bar
, and Foo
. Specifically, after it creates Bar
it will give it the Bim
it already created. Then after Application creates Foo
it will give it the Bar
it already created.
i.e. The control of the dependencies is inverted from the one being called to the one doing the calling.
Compare this to the general definition of dependency injection: "Basically, instead of having your objects creating a dependency or asking a factory object to make one for them, you pass the needed dependencies into the object externally, and you make it somebody else’s problem." - this could be with constructors, settings, or dependency injection frameworks.
In Laravel, the Service Container is responsible for injecting dependencies of your classes via their constructors. Its responsibilities are to:
Here's an example of how a constructor receives a dependency (for UserRepository
below):
<?php
class UserController extends Controller
{
protected $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
Usually, laravel automatically figures out how to construct the object. But you can give the container clearer instructions in the ServiceProvider:
<?php
class MyServiceProvider extends ServiceProvider
{
public function register()
{
// Within a service provider, you can access the container with `$this->`
// A closure (the function) returns an instance of the class (this one is
// a singleton because we used $this->app->singleton)
$this->app->singleton(\App\Service\Geocoder::class, function ($app) {
return new \App\Service\Geocoder($app->make('Utils\HttpRequests'));
});
// To do a non-singleton, use `$this->app->bind`
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
}
}
Laravel allows us to substitute dependencies. This is most useful when we program to an abstract interface (e.g. EventPusher
but substitute concrete instances in different environments - e.g. RedisEventPusher
)
<?php
// This statement tells the container that it should inject the RedisEventPusher when a class needs an implementation of EventPusher.
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
This can even be contextual, such that depending on when (i.e. from what class) we ask for the dependency, a different one will be provided:
<?php
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});