How does Service Container know when a constructor is triggered to resolve its dependencies?

370 Views Asked by At

For example I have a class named User and in its constructor I have a dependency named MyDependency. MyDependency does not have any dependencies itself so I don't need to bind it to he $app container.

When I instantiate the User class with no constructor parameters, where does laravel know that a constructer is triggered and it must resolve its dependencies?

I know that Service Container uses ReflectionClass to instantiate the Dependency. But there must be some function which gets triggered to perform this.

1

There are 1 best solutions below

4
Alex Yokisama On

It doesn't. You have to explicitly call App::make() to create an instance. The same goes for calling other methods using App:call(). Here's the relevant section of the docs: https://laravel.com/docs/10.x/container#the-make-method

UPD Assume the following controller:

<?php

namespace App\Http\Controllers;

use App\Models\Order;
use App\Services\OrderService;

class OrderController extends Controller
{
    protected OrderService $service;

    public function __construct(OrderService $service) {
        $this->service = $service;
    }

    public function update(Order $order)
    {
        return $this->service->update($order);
    }
}

The contents of OrderService:

<?php 

namespace App\Services;

use App\Models\Order;

class OrderService {
    public function __construct(bool $dry) { /* */ }
    public function update(Order $order) { /* */ }
}

In your service provider you have:

$this->app->when(OrderService::class)->needs('$dry')->give(false);

And in your routes/api.php

Route::get('/order/{order}', [OrderController::class, 'update']);

Now what happens when make a request to api/order/1? First, the OrderController instance is created. You provided the class name when registering the route, so Laravel knows which class to use. The instances is created with a call of make. As you mention in the quetion, reflections are used to determine, what arguments are expected by the constructor. In our case it's just the instance of OrderService. So now Laravel will try to resolve this dependency. It's a class, not an interface, so Laravel will simply try to create it's instance, again using make. Again, analysing the constructor it will determine that it needs boolean parameter $dry. For which we have registered a resolver in our service provider. So $dry gets passed to the OrderService constructor, Laravel gets an instance and passes it to OrderController constructor. Now it can call the update method. By the same logic, the call method is used to resolve all dependencies. In this case it's just model dependency, which Laravel can resolve out of the box.

The general idea here is that make and call are always being called somewhere under the hood. There's no mechanism to intercept a native call to constructor or method. For example, if you modify routes/api.php like this:

Route::get('/order/{order}', function($order) {
    $controller = new OrderController();
    return $controller->update($order);
});

You'll get an ArgumentCountError for the constructor call.