skip to content

In Laravel, what does the container's call() method do, and how does it fill the parameters of the method it invokes?

level: middleimportance: should knowfreq 38%

answer

  1. method injection, not constructor injection
  2. closure, [object, 'method'], 'Class@method'
  3. named overrides are checked first
  4. class types resolved through make()
  5. Laravel 13: unbound nullable default stays null

basics

~20 s

App::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
<?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

for a junior

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.

for a middle

Explain the fill order: name-keyed overrides, class-name overrides, attributes, container resolution, defaults, and the error for an unfillable required parameter.

for a senior

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.

for a principal

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.