Scopes
A scope defines how long a resolved instance lives and whether it is shared. There are three:
singleton, transient, and request. A class picks one via attribute or binding; the
default is transient.
The three scopes
| Scope | Identity | Cached | Set by |
|---|---|---|---|
singleton |
one per process | yes, after first make() |
#[Singleton] · singleton() |
transient |
new every resolution | no | #[Transient] · bind() · transient() · (default) |
request |
one per request / coroutine | per request | #[Request] · request() |
singleton
Created on the first make(), cached in the container, returned on every later call.
Use for stateless services — repositories, factories, connection pools, config readers. Avoid for classes holding per-request state; that leaks across requests under Swoole.
transient
No caching — every make() / injection returns a fresh object. This is the default when a
class has no scope attribute and no manual binding.
Use for stateful, non-shared objects — query builders, form objects, DTOs, units of work.
request
One instance per HTTP request, keyed by runtime:
| Runtime | Behaviour |
|---|---|
| Swoole | Stored in Coroutine::getContext()['__di'] — fully isolated per coroutine. Each concurrent request gets its own instance, cleaned up when the coroutine ends. |
| FPM | Equivalent to singleton — one request = one process, so the process-level cache is correct. |
| CLI | Equivalent to singleton — one command = one process. |
Use for per-request state — auth context, current user, unit of work, request-bound counters.
The request scope is the one place ext-swoole matters; see
Request scope & Swoole.
Precedence
A manual registration always overrides a class attribute — the last binding wins.
#[Singleton]
class UserService {}
// Override to transient for a specific context (e.g. tests)
$c->transient(UserService::class);
// → UserService is now transient regardless of #[Singleton]Overrides skip the cache entirely
make($abstract, $overrides) with a non-empty $overrides array always builds a fresh
instance and neither reads nor writes the singleton/request cache — regardless of scope. Use
make() with no overrides to get the shared instance.
Swoole safety matrix
Never place mutable per-request data in a singleton under Swoole — it leaks across concurrent
requests sharing the worker’s memory.
| Class type | Recommended scope |
|---|---|
| DB connection pool | singleton |
| Repository (stateless) | singleton |
| Config reader | singleton |
| Auth context | request |
| Current user | request |
| Unit of work | request |
| Query builder | transient |
| DTO / value object | transient |
| Form object | transient |
Related
- Attributes — the scope-attribute reference
- Request scope & Swoole — coroutine isolation in depth
- Mental model — why the default is
transient