-
Notifications
You must be signed in to change notification settings - Fork 0
Autowiring
Autowiring is the container's ability to build a class without an explicit
registration, by inspecting its constructor and resolving each dependency for
you. It is what makes get(SomeClass::class) work out of the box.
Pass any existing class name to get():
use InitPHP\Container\Container;
class Engine {}
class Car
{
public function __construct(public Engine $engine) {}
}
$container = new Container();
$car = $container->get(Car::class);
$car instanceof Car; // true
$car->engine instanceof Engine; // trueThe container reads Car::__construct(), sees it needs an Engine, resolves
Engine (which has no dependencies of its own) and injects it.
Dependencies are resolved recursively to any depth:
class Db {}
class Repository
{
public function __construct(public Db $db) {}
}
class Controller
{
public function __construct(public Repository $repository) {}
}
$controller = $container->get(Controller::class);
$controller->repository->db instanceof Db; // trueBecause every entry is cached (see Resolution & Caching), a dependency shared by several classes is the same instance everywhere:
$repository = $container->get(Repository::class);
$controller = $container->get(Controller::class);
$controller->repository === $repository; // trueFor every constructor parameter, the container applies these rules in order:
-
Class-typed parameter — a single, non-builtin type the container knows
about (an existing class, or a registered identifier): resolved through
get(). - Build failed but parameter is optional — if step 1 throws a container error and the parameter has a default value or is nullable, the fallback from steps 3–4 is used instead of propagating the error.
- Default value available — the parameter's default value is used.
-
Nullable parameter —
nullis injected. -
None of the above — a
DependencyHasNoDefaultValueExceptionis thrown.
class Service
{
public function __construct(
public Engine $engine, // rule 1: autowired
public string $name = 'svc', // rule 3: uses 'svc'
public ?Engine $spare = null, // rule 1 if resolvable, else rule 4
) {}
}
$service = $container->get(Service::class);
$service->engine instanceof Engine; // true
$service->name; // 'svc'
$service->spare instanceof Engine; // true (Engine is resolvable)A scalar parameter without a default cannot be guessed and fails:
class NeedsString
{
public function __construct(public string $value) {}
}
$container->get(NeedsString::class); // throws DependencyHasNoDefaultValueExceptionRegister the value or provide a factory instead — see Binding & Factories.
A parameter that is nullable or has a default is treated as optional. If the container cannot build its type — for any reason: the class needs a scalar, an abstract class, a missing binding — the fallback is used rather than failing:
class Connection
{
public function __construct(public string $dsn) {} // not autowirable
}
class Cache
{
// Connection can't be built (needs $dsn), but the parameter is optional,
// so the container falls back to null.
public function __construct(public ?Connection $connection = null) {}
}
$container->get(Cache::class)->connection; // nullIf the same parameter were required (Connection $connection, no default,
not nullable), the underlying failure would propagate instead.
A union (A|B) or intersection (A&B) typed parameter is not autowired —
the container cannot decide which type to build. It falls back to the default
value or null:
class WithUnion
{
public function __construct(public int|string $value = 1) {}
}
$container->get(WithUnion::class)->value; // 1If such a parameter has no default and is not nullable, a
DependencyHasNoDefaultValueException is thrown. Provide a factory for these
cases (see Service Factories).
A class with no constructor is simply instantiated:
class Plain {}
$container->get(Plain::class) instanceof Plain; // true| Target | Result | Why |
|---|---|---|
| Interface (unbound) |
NotFoundException when requested directly; DependencyHasNoDefaultValueException as a required dependency |
class_exists() does not report interfaces, so the container treats an unbound interface as unknown. Bind it first.
|
| Abstract class | DependencyIsNotInstantiableException |
class_exists() reports it, so the container tries to build it and fails because it is not instantiable. |
| Non-public constructor | DependencyIsNotInstantiableException |
Reflection reports the class as not instantiable. |
| Circular dependency | CircularDependencyException |
A → B → A would otherwise recurse forever. |
See Exceptions for the full hierarchy and messages.
- Binding & Factories — make the un-autowirable cases work.
- Resolution & Caching — the lifecycle and singleton behaviour.
- Limitations — what autowiring intentionally does not do.
initphp/container · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Usage
Reference
Practical Guides
Migration & Help