In Laravel, how do app(), resolve() and App::make() relate, and why does passing parameters to make() skip a singleton's cached instance?
answer
- one resolution path, several spellings
- app() with no argument: the container
- makeWith() forwards to make()
- overrides keyed by constructor argument name
- parameters force an uncached one-off build
basics
~10 sapp(X), resolve(X), App::make(X) and $this->app->make(X) all run the application container's make(). Passing a parameter array forces a one-off build: the container ignores the cached singleton and does not cache the new object.
solid answer
~40 s`app()` with no argument returns the container itself, the `Illuminate\Foundation\Application` instance; `app(X, $params)` calls its `make()`. The `resolve()` helper simply calls `app()`, `App::make()` reaches the same object through the facade, and `makeWith()` forwards to `make()`. The parameter array is keyed by constructor argument name and fills those arguments; for a closure binding it arrives as the closure's second argument. Because supplied values could change the result, the container treats the call as a one-off build: it skips the shared instance, builds a new object, and neither caches it nor marks the type resolved. So `app(RateCard::class, ['zone' => 3])` on a singleton returns an unshared copy, while a later plain `app(RateCard::class)` still returns the shared one.
code
php · 13 lines<?php
use App\Services\RateCard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;
App::singleton(RateCard::class, fn (Application $app, array $params) => new RateCard($params['zone'] ?? 1));
$a = app(RateCard::class); // built and cached (zone 1)
$b = resolve(RateCard::class); // the cached object
$c = app(RateCard::class, ['zone' => 3]); // one-off build, not cached
var_dump($a === $b, $a === $c); // bool(true) bool(false)go deeper
Recall that app(), resolve(), App::make() and $this->app->make() all reach the same container, and that app() alone returns the container.
Explain name-keyed parameter overrides, how a closure binding receives them, and why a call with parameters neither reads nor writes the singleton cache.
Spot designs that pass per-call arguments to a shared service and replace them with a factory or separate bindings, rather than relying on uncached one-off builds.
Decide where service location through app() is acceptable in a codebase, such as providers and factories, and where constructor injection must be the rule.
## One resolution path, several spellings Laravel offers many ways to ask the container for an object, and they all end in the same method. The **application** object (`Illuminate\Foundation\Application`) *is* the container: it extends `Illuminate\Container\Container`, registers itself under `app` and the container contracts, and is reachable globally. | Spelling | Defined in | What it does | |---|---|---| | `app()` | `Foundation/helpers.php` | Returns the container instance | | `app(X, $params)` | same | Calls `make(X, $params)` on the container | | `resolve(X, $params)` | same | Calls `app(X, $params)` | | `App::make(X)` | `App` facade | Forwards to the container's `make()` | | `$this->app->make(X)` | inside a service provider | Direct call on the container | | `makeWith(X, $params)` | `Container` | Calls `make(X, $params)` | | `get(X)` | `Container` (PSR-11) | Resolves `X` with no parameters | There is no second cache or second container behind any of these. Choosing between them is style: a provider uses `$this->app`, application code usually prefers constructor injection, and the helpers are for places that cannot receive injection. ## How the parameter array is matched The optional second argument is an associative array of **overrides**: - Keys are matched against constructor parameter **names**, not positions or types. `['zone' => 3]` fills `int $zone`. - Parameters without a matching key are resolved as usual: class types are auto-wired, scalars take their defaults. - Keys that match no parameter are ignored. - For a binding whose concrete is a **closure**, the array is passed to the closure as its second argument, `function (Application $app, array $params)`. - For `bind(Interface::class, Impl::class)`, the container resolves `Impl` with the same array, so the keys must match `Impl`'s constructor. ## Why parameters bypass the shared instance Inside the container, resolution follows these steps: 1. Look up any contextual binding for the class currently being built. 2. Decide whether this is a **contextual build**: true when a parameter array is non-empty or a contextual concrete was found. 3. If a cached instance exists **and** this is not a contextual build, return the cached instance. 4. Otherwise build the concrete, applying any `extend()` decorators. 5. If the binding is shared **and** this is not a contextual build, cache the result and mark the type resolved. Steps 3 and 5 are the key: supplying parameters disables both the read from and the write to the shared cache. The container cannot know whether `['zone' => 3]` would produce the same object as the cached one, so it builds a private copy and leaves the shared one alone. ## Consequences in practice - `app(RateCard::class, ['zone' => 3])` on a singleton returns a **new, unshared** `RateCard` every call. - The shared `RateCard` is unaffected. A later `app(RateCard::class)` returns it, building it first if nobody has yet. - A singleton that needs different arguments per caller is usually two different things: a factory that takes the argument, or separate bindings. - `makeWith()` exists for readability only; `make()` accepts the same array. ## When to call make() yourself Explicit resolution has a few legitimate homes: 1. **Service providers**, where a binding closure builds an object from other services: `fn (Application $app) => new QuoteService($app->make(RateCard::class))`. 2. **Factories** that must create objects on demand at run time, where the number or type of objects is not known when the factory itself is built. 3. **Code that cannot receive injection**, such as a plain helper function or a static method. Everywhere else, **constructor injection** is the better default: the class declares what it needs, the container supplies it, and a test can pass a hand-made object without touching the container at all. When a class calls `app()` in the middle of a method, its real dependencies are no longer visible in its signature. ## PSR-11 get() `get(string $id)` resolves without parameters. If resolution fails and the id was never bound, it throws `Illuminate\Container\EntryNotFoundException`, which implements PSR-11's not-found interface; for a bound id the original exception is rethrown.
- In Laravel, what does the PSR-11 get() method on the container do differently from make()?`get()` takes no parameter array and runs the same resolution. If resolution fails and the id was never bound, it wraps the failure in `Illuminate\Container\EntryNotFoundException`, a PSR-11 not-found exception; if the id is bound, the original exception is rethrown. It lets framework-agnostic libraries type-hint `Psr\Container\ContainerInterface` and still use Laravel's container.
- In Laravel, how are make() parameters matched when the binding's concrete is a class name rather than a closure?For `bind(Interface::class, Impl::class)` the container wraps the class in a closure that resolves `Impl` with the same parameter array. The keys therefore have to match `Impl`'s constructor argument names; unmatched keys are ignored and the remaining arguments are auto-wired.
saying these in an interview costs you the question
- Passing parameters to make() updates the cached singleton with the new values.
- The make() parameter array is matched to constructor arguments by position.
- resolve() uses a separate container with its own cache of instances.
- Calling app() with no arguments returns the current HTTP request.
- make() ignores parameters, so makeWith() is required to pass any.