The Service Container

The Service Container and Zero-Configuration Resolution

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:

Tracing two resolutionsPHP
$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);
Output
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\TillCheckout

Checkout 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.