Nested run time dependency injection using Laravel Service Container

Updated: Feb 17, 2025

Nested run time dependency injection using Laravel Service Container

In Laravel, the Service Container is a powerful tool used for managing and resolving dependencies between classes. It allows us to define services, bind interfaces to classes, and register singletons, making it easier to manage the application's dependencies.

When it comes to nested runtime dependency injection, Laravel provides a flexible way to achieve this using the Service Container's binding and resolution mechanisms.

Let's first understand the concept of runtime dependency injection and how it differs from static dependency injection.

Runtime dependency injection is a technique where the dependencies are resolved and injected at runtime, while static dependency injection is where the dependencies are resolved and injected during the class instantiation.

In Laravel, we can achieve runtime dependency injection by using the Service Container to resolve dependencies on-the-fly. This can be useful when we have nested dependencies, where one service depends on another service that itself depends on another service.

To illustrate this, let's consider an example where we have three services: A, B, and C. A depends on B, and B depends on C.

First, we need to define the services in the app/Services directory:

// app/Services/A.php
namespace App\Services;

class A
{
    protected $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }

    // ...
}

// app/Services/B.php
namespace App\Services;

class B
{
    protected $c;

    public function __construct(C $c)
    {
        $this->c = $c;
    }

    // ...
}

// app/Services/C.php
namespace App\Services;

class C
{
    // ...
}

Next, we need to register the services in the app/Providers/AppServiceProvider.php file:

// app/Providers/AppServiceProvider.php
namespace App\Providers;

use App\Services\A;
use App\Services\B;
use App\Services\C;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(A::class, function ($app) {
            return new A($app->make(B::class));
        });

        $this->app->singleton(B::class, function ($app) {
            return new B($app->make(C::class));
        });

        $this->app->singleton(C::class);
    }
}

In the register() method, we register A, B, and C services using the singleton() method. For A and B, we pass a closure that returns a new instance of the service with the dependency injected.

Finally, we can use the services in our application:

// app/Http/Controllers/HomeController.php
namespace App\Http\Controllers;

use App\Services\A;

class HomeController extends Controller
{
    protected $a;

    public function __construct(A $a)
    {
        $this->a = $a;
    }

    public function index()
    {
        return $this->a->doSomething();
    }
}

In the HomeController constructor, we inject the A service, which in turn depends on the B service, which depends on the C service. Laravel's Service Container handles the dependency resolution, ensuring that all the services are properly instantiated and injected.

In summary, Laravel's Service Container provides a flexible way to manage nested runtime dependency injection. By registering services with dependencies using closures, we can ensure that the dependencies are properly resolved and injected at runtime, even when we have nested dependencies.