In Laravel's service container, how do bind(), singleton(), scoped() and instance() differ in what repeated resolutions return?
answer
- one shared flag per binding
- bind: a new object per make()
- singleton: built lazily, then cached
- scoped: dropped by forgetScopedInstances()
- instance: your pre-built object, stored now
basics
~20 sbind() builds a new object on every resolution; singleton() builds once, lazily, and returns that object afterwards; scoped() is a singleton forgotten when a queue job or Octane request starts; instance() stores an object you already built.
solid answer
~40 sAll four register into the same container. `bind()` stores a non-shared binding, so each `make()` runs the closure or builds the class again. `singleton()` is `bind()` with the shared flag set: nothing is built at registration, the first resolution builds the object and later ones get the cached copy. `scoped()` is a singleton whose key is also listed as scoped; `forgetScopedInstances()` drops those copies, and the queue worker calls it before fetching each job while Octane does so after each request. `instance()` takes an object you already built and stores it immediately. Under PHP-FPM each request boots a fresh application, so a singleton lives one request anyway. The `bindIf()`, `singletonIf()` and `scopedIf()` variants register only when the type is not already bound.
code
php · 11 lines<?php
use App\Services\QuoteBuilder;
use App\Services\RateTableClient;
use Illuminate\Support\Facades\App;
App::bind(QuoteBuilder::class);
App::singleton(RateTableClient::class);
var_dump(app(QuoteBuilder::class) === app(QuoteBuilder::class)); // bool(false)
var_dump(app(RateTableClient::class) === app(RateTableClient::class)); // bool(true)go deeper
Recall the four calls and what a second resolution returns for each: a new object, the cached object, the cached object until the scope resets, or your own object.
Explain the shared flag, lazy versus eager construction, which processes flush scoped instances, and why the If variants exist for packages.
Choose lifetimes by state: stateless clients as singletons, per-request or per-job state as scoped, and recognise when a singleton will carry state from one queued job to the next.
Set a team default for lifetimes that stays correct if the app later moves to long-lived workers, and decide which services must be reviewed before that move.
## One registry, one shared flag Laravel's container keeps a table of **bindings**: for each **abstract** (a class name, interface name or string key) it stores a **concrete** (a closure or class name) and a boolean **shared** flag. It also keeps a table of **instances**, the objects it has cached. The registration methods differ only in how they fill those two tables: - `bind($abstract, $concrete = null, $shared = false)` writes a binding with `shared = false`. - `singleton($abstract, $concrete = null)` calls `bind()` with `shared = true`. - `scoped($abstract, $concrete = null)` records the abstract in a list of **scoped** keys, then calls `singleton()`. - `instance($abstract, $object)` skips the bindings table and writes the object straight into the instances table. When you call `make()`, the container first looks for a cached instance; if there is none it builds the concrete, and it caches the result only when the binding is shared. ## The four calls side by side | Method | Object built | Repeated `make()` returns | Typical use | |---|---|---|---| | `bind()` | On every resolution | A new object each time | Stateful or cheap objects, per-use builders | | `singleton()` | On first resolution | The same object | Clients, configured services, expensive setup | | `scoped()` | On first resolution in a lifecycle | The same object until the scope is flushed | State tied to one request or one job | | `instance()` | Before registration, by you | The object you passed | Pre-configured objects, test doubles | If you omit the concrete, the abstract is bound to itself and auto-wired. If you pass **only a closure** with a declared return type, `bind()` reads the return type and registers the binding under that class. ## How scoped() differs from singleton() The difference only shows up when one PHP process handles more than one unit of work: 1. **PHP-FPM**: every HTTP request bootstraps a new application, so singletons and scoped instances both die with the request. They behave identically. 2. **Queue worker** (`queue:work`): one process runs many jobs. The worker's reset step calls `forgetScopedInstances()` before it fetches each job, so a scoped service starts fresh per job while a singleton carries over from job to job. 3. **Octane**: one worker serves many requests. Octane flushes scoped instances after each request; singletons resolved earlier persist. So `scoped()` is the choice for something that must be shared within one request or job but never leak into the next, such as a per-request tenant resolver. ## Conditional registration `bindIf()`, `singletonIf()` and `scopedIf()` first call `bound()`, which is true when the abstract has a binding, a cached instance or is an alias. If it is already bound they do nothing. Packages use them so an application that registered its own implementation first is not overwritten. ## Re-registering and other gotchas - Calling `bind()` or `singleton()` again for an abstract **drops the cached instance**, so the next resolution uses the new binding. If the abstract had already been resolved, any `rebinding()` callbacks run. - `singleton()` is **lazy**: registering it in a provider builds nothing. Only `instance()` is eager, because you built the object yourself. - An `extend()` decorator runs on each resolution for a non-shared binding but once for a shared one, since the decorated object is what gets cached. - The attribute forms of these lifetimes (`#[Singleton]`, `#[Scoped]`, `#[Bind]`) live on the class or interface itself; they are an alternative to provider calls, not a different lifetime. ## Choosing a lifetime A simple rule of thumb covers most services: - **Stateless and cheap**: either works; `bind()` is the honest default because nothing needs sharing. - **Stateless but expensive to build** (an HTTP client with configured base URLs, a parsed rate table): `singleton()`. - **Holds state for one unit of work** (the current tenant, a per-request cache of lookups): `scoped()`, so a queue worker or Octane does not carry it into the next job or request. - **Already built elsewhere**, or a hand-made test double: `instance()`. The mistake to avoid is a `singleton()` that stores something about the current request or job. Under PHP-FPM it looks correct, because the application is rebuilt for every request, and it starts misbehaving once the same code runs in a queue worker or under Octane. ```php $this->app->bind(QuoteBuilder::class); // new object each time $this->app->singleton(RateTableClient::class); // one object, built on first use $this->app->scoped(TenantContext::class); // one per request or job $this->app->instance(FuelSurcharge::class, new FuelSurcharge(0.12)); // stored now ```
- In Laravel, a singleton was already resolved and a provider then calls bind() for the same abstract; what happens to the cached object?`bind()` drops the stale cached instance for that abstract, so the next resolution builds from the new binding. Because the abstract had been resolved, the container also fires any `rebinding()` callbacks registered for it, passing them a freshly resolved object. Objects that already captured the old instance keep it.
- Under PHP-FPM, does a Laravel singleton() binding keep its object between two HTTP requests?No. Each FPM request bootstraps a new application and container, so a singleton lives for that request only. Cross-unit sharing appears when one process handles many units of work, as a queue worker does with jobs or Octane does with requests, which is where `scoped()` earns its place.
- In Laravel, how can bind() register a type without naming the abstract?Pass only a closure with a declared return type, for example `App::bind(function (Application $app): QuoteBuilder { ... })`. The container reads the closure's return type and registers the binding under that class; a union return type registers it under each class in the union.
bind() is a bakery that bakes a fresh loaf for every customer; singleton() bakes one loaf at the first order and slices it for everyone after; scoped() bakes one shared loaf per shift and bins it when the shift ends; instance() is a loaf you brought from home and left on the counter.
saying these in an interview costs you the question
- singleton() builds the object as soon as the provider registers it.
- A singleton binding is shared across all HTTP requests under PHP-FPM.
- scoped() gives each class that injects the service its own instance.
- instance() takes a class name and builds the object lazily.
- singletonIf() replaces whatever binding already exists for the type.