The application object is the container (Bootstrapping), and app(Checkout::class), app()->make(), App::make() and resolve() all end in its make() method. A class without a binding needs no registration. make() loads any deferred provider, returns a shared instance if one exists, and picks the concrete (contextual binding, bind(), #[Bind], else the class). Then, like the container of Dependency Injection, it reflects: isInstantiable(), then getConstructor()->getParameters(), resolving each class-typed parameter recursively.
beforeResolving() and afterResolving() callbacks expose the recursion. The second call also shows the contextual binding of Contextual Binding:
$d = 0;
app()->beforeResolving(function (string $abstract) use (&$d) {
if (str_starts_with($abstract, 'App')) echo str_repeat(' ', $d++), "make $abstract\n";
});
app()->afterResolving(function (object $o) use (&$d) {
$c = $o::class;
if (str_starts_with($c, 'App')) echo str_repeat(' ', --$d), "built $c\n";
});
app(App\Services\Checkout::class);
app(App\Services\TillCheckout::class);make App\Services\Checkout
make App\Contracts\PaymentGateway
built App\Services\FakeGateway
built App\Services\Checkout
make App\Services\TillCheckout
make App\Contracts\PaymentGateway
make App\Services\CashDrawer
built App\Services\CashDrawer
built App\Services\CashDrawer
built App\Services\TillCheckoutCheckout got FakeGateway through a binding (Binding Interfaces); CashDrawer reports twice, as itself and as the PaymentGateway it stands in for. A scalar without a default stops the build: for a constructor taking int $min, make() threw "Unresolvable dependency resolving [Parameter #0 [ <required> int $min ]] in class Threshold". app()->makeWith(Threshold::class, ['min' => 3]) supplies it by name; a contextual attribute (Contextual Binding) declares it.