In Laravel, what does the container's call() method do, and how does it fill the parameters of the method it invokes?
answer
- method injection, not constructor injection
- closure, [object, 'method'], 'Class@method'
- named overrides are checked first
- class types resolved through make()
- Laravel 13: unbound nullable default stays null
basics
~20 sApp::call() invokes any callable and injects its parameters: values you pass by name win, class-typed parameters are resolved from the container, and the rest take their defaults. Laravel uses it to run a job's handle(), an Artisan command's handle() and scheduled closures.
solid answer
~40 s`call($callback, $parameters = [], $defaultMethod = null)` accepts a closure, an `[$object, 'method']` array, a `'Class@method'` string (the class is built with `make()` first) or an invokable class name, which defaults to `__invoke`. For each parameter it tries, in order: a value in `$parameters` keyed by the parameter's name, then a contextual attribute on the parameter; for a class type, a value keyed by the class name, then `make()` on the class (unless the parameter has a default and nothing binds that class); otherwise a plain default. A required parameter it cannot fill throws `BindingResolutionException` (`Unable to resolve dependency`). The bus dispatcher runs a job's `handle()` this way, and Artisan does the same for a command's `handle()`. In Laravel 13, an unbound `?Carbon $date = null` receives `null`; Laravel 12 injected a Carbon instance.
code
php · 17 lines<?php
namespace App\Reports;
use App\Services\RateTableClient;
class CarrierReport
{
public function build(RateTableClient $rates, string $carrier = 'ground'): array
{
return $rates->zonesFor($carrier);
}
}
// Elsewhere:
// App::call([new CarrierReport, 'build'], ['carrier' => 'freight']);
// $rates is resolved from the container; $carrier comes from the named override.go deeper
Recall that call() runs a method or closure with its type-hinted services injected, and that job and command handle() methods get their services this way.
Explain the fill order: name-keyed overrides, class-name overrides, attributes, container resolution, defaults, and the error for an unfillable required parameter.
Know the Laravel 13 change for nullable class defaults and audit code that relied on optional parameters being auto-wired, especially in jobs and scheduled closures.
Judge when method injection clarifies a class and when it hides dependencies that belong in the constructor, and set a consistent convention for handlers.
## What call() is for **Constructor injection** covers objects the container builds. `call()` covers the other case: you already have an object, or just a closure, and want to **invoke a method** while the container supplies its arguments. This is **method injection**. The signature on `Illuminate\Container\Container` is: ```php public function call($callback, array $parameters = [], $defaultMethod = null) ``` It is also reachable as `App::call()` or `app()->call()`. ## Accepted callable forms | Form | Example | What happens | |---|---|---| | Closure | `App::call(fn (RateTableClient $c) => $c->zones())` | The closure's parameters are injected | | Object and method | `App::call([$report, 'build'])` | `build()` on the existing object is injected | | `Class@method` string | `App::call('App\Reports\CarrierReport@build')` | The class is built with `make()`, then `build()` is called | | Invokable class name | `App::call(CarrierReport::class)` | Built with `make()`, then `__invoke()` is called | | Class name plus default method | `App::call(CarrierReport::class, [], 'build')` | Built with `make()`, then `build()` is called | ## How each parameter is filled For every parameter of the target, `call()` works through this order and stops at the first match: 1. A key in `$parameters` equal to the parameter's **name** (`['carrier' => 'freight']` fills `$carrier`). 2. A **contextual attribute** on the parameter, such as a config or storage attribute; these resolve through the attribute's own logic. 3. For a **class-typed** parameter: - a key in `$parameters` equal to the **class name**; - for a variadic, whatever `make()` returns for that class; - if the parameter has a **default** and the class is **not bound**, the default; - otherwise `make()` on the class. 4. For an untyped or scalar parameter, its **default value**. 5. If a required parameter is still unfilled, throw `BindingResolutionException` with `Unable to resolve dependency [...]`. Leftover values in `$parameters` that matched nothing are appended after the resolved ones, so a closure can still receive extra positional values. ## The Laravel 13 change for nullable class defaults Step 3's default rule is what changed in 13.0. The upgrade guide rates it low impact: ```php $container->call(function (?Carbon $date = null) { return $date; }); // Laravel 12 and earlier: a Carbon instance // Laravel 13: null ``` Constructor injection already behaved this way; Laravel 13 made `call()` match it. Code that relied on receiving an auto-wired object for an optional, unbound class parameter must now remove the default or bind the class. ## Where the framework uses it - **Jobs**: when a job has no separate handler, the bus dispatcher runs `$container->call([$command, 'handle'])`, which is why a job's `handle()` can type-hint services that were never serialized into the payload. - **Artisan commands**: `Command::execute()` calls `handle()` (or `__invoke()`) through the container, so commands also get method injection. - **Scheduled closures**: a closure scheduled with the scheduler's `call()` is invoked through the container with its parameters injected. - **Controller actions** use the router's own method-dependency resolver rather than `call()`, with similar rules for route parameters and type-hinted services. ## Method injection versus constructor injection Both come from the same container, but they suit different dependencies: - **Constructor injection** suits collaborators the object uses in most of its methods; they become part of the object's state. - **Method injection** suits collaborators needed by one method only, or objects that are serialized, such as a queued job: its constructor holds the data to process, and its `handle()` receives the services at run time, so nothing heavy ends up in the queue payload. - A job that type-hints a database client in its constructor would try to serialize it; type-hinting it on `handle()` avoids that entirely. ## Pitfalls - Calling `$job->handle()` directly in plain PHP injects **nothing**; only the container's `call()` adds injection. - Scalars are never guessed: pass them by name, or give them defaults. - Name keys win over everything, so a stray key with the same name as a service parameter silently replaces the injected service.
- In Laravel, can you pass a call() override keyed by class name instead of parameter name?Yes. If the parameter array has a key equal to a parameter's class name, `call()` uses that value for the type-hinted argument, which lets you hand in a specific object. Name keys are checked first, then class-name keys, before the container resolves anything itself.
- In Laravel, what does App::call('App\Reports\CarrierReport@build') do step by step?It splits the string on `@`, resolves `CarrierReport` through `make()` so its constructor is auto-wired, then calls `build()` on that object with method injection. A bare class-name string works only if the class has `__invoke()` or you pass the method name as the third argument.
saying these in an interview costs you the question
- call() works only on closures, not on methods of existing objects.
- Values passed to call() are matched to parameters by position.
- Calling $job->handle() directly in PHP still gets its services injected.
- In Laravel 13 an unbound nullable class parameter defaulting to null still gets an instance.
- Services type-hinted on a queued job's handle() are serialized with the job payload.