Repository navigation
Description
Problem
Quantum\Di\DiContainer::resolveParameter() reads a parameter type only when it is a ReflectionNamedType. For a union (A|B) or an intersection (Reader&Writer) type the type is ignored, so the parameter falls through to the passed arguments, then the default value, then null.
Result (checked on PHP 8.1):
public function __construct(A|B $dep)fails with aTypeErrorthat gives no hint about the container.public function __construct(A|B|null $dep)silently receivesnull, even whenAis registered in the container.Reader&Writeris never resolved, even when an implementing class is registered.
Proposal
Resolve a union or intersection parameter only when the choice is unambiguous. Never guess from the declaration order and never pass null silently.
- Union: drop
nulland builtin members. If exactly one remaining member can be resolved (it is registered in the container, or it is an instantiable class), use it. If several can, do not pick one. - Intersection: use a registered or instantiable member and check that the result satisfies every part of the type (
instanceoffor each). A result that does not satisfy all parts is not used. - Otherwise: keep the existing order (passed argument, default value). If neither exists, throw a
DiExceptionwith a clear message (new named constructor and message template insrc/Di/Enums/ExceptionMessages.php) that names the class, the parameter and the type, instead of aTypeErroror a silentnull. - The same rules apply to
autowire()of a closure, which sharesresolveParameter().
Why not the order of declaration
Other containers differ: one rejects union types (Marko, a clear exception), one tries members in declaration order (Tempest, which also does not check that an intersection is satisfied as a whole), one tries each branch and verifies it (Canvas). Declaration order makes A|B and B|A behave differently, so it is not used here.
Acceptance
- Tests in
tests/Unit/Di/for: union with one registered member, union with none, union with several resolvable members,A|B|nullwith and without a default, intersection with a registered implementation that satisfies all parts, intersection that does not, closure autowiring. CHANGELOG.mdentry. A parameter that failed before with aTypeErrornow resolves or throws aDiException; the exception type changes, so it is noted as a behavior change.
Milestone 3.2.0 (a different exception type on a path that used to throw TypeError).
Activity