Di
The Di package is Quantum's runtime dependency injection container. It keeps class bindings in a registry, resolves constructor dependencies with reflection, and optionally caches shared instances for the current app context.
The package is built from three classes:
Quantum\Di\Diis the static facade.Quantum\Di\DiContainerperforms registration, resolution, and instance caching.Quantum\Di\DiRegistrystores validated abstract-to-concrete bindings.
Entry points
Di facade
Quantum\Di\Di does not keep its own global container. Every static call is forwarded to the container returned by App::getContext()->getContainer().
That means the static API is only a facade over the current app context. If the app context swaps containers, later Di::... calls will use the new container.
DiContainer
DiContainer is the real runtime object. It exposes the package API:
registerDependencies()register()isRegistered()has()set()get()create()autowire()reset()resetContainer()
Registration model
register(string $concrete, ?string $abstract = null)
register() stores a binding in DiRegistry.
- With only
$concrete, the class is registered against itself. - With both arguments, the abstract key points to the concrete class.
- The concrete class must exist and be instantiable.
- When an abstract key is provided, it must be an existing class or interface.
- Re-registering the same abstract key throws
DiException::dependencyAlreadyRegistered(...).
registerDependencies(array $dependencies)
registerDependencies() iterates the input map and only registers keys that are not already present. Existing bindings are skipped instead of replaced.
Resolution model
Shared instances: get()
get() requires the dependency to be registered first. It then resolves the binding and caches the created object under the abstract key.
Repeated get() calls for the same abstract return the same instance for that container.
Fresh instances: create()
create() always returns a newly instantiated object.
If the dependency is not registered yet, create() first registers it against itself and then resolves it without caching the result in the container.
Manual instances: set()
set() inserts an already-built object into the container.
Important rules from the current implementation:
- The abstract must be an existing class or interface.
- The instance must satisfy
is_a($instance, $abstract). - If the container already has an instance for that abstract,
set()always throws, regardless of the$overrideflag. - If the abstract is not registered yet,
set()auto-registers the instance class against that abstract. $override = falseonly blocks inserting an instance when a registry binding already exists.- With the default
$override = true,set()may add a container instance even when a registry binding already points somewhere else.
Autowiring behavior
autowire() resolves parameters for a callable and returns the argument list. It does not invoke the callable.
Supported callable forms are limited to:
- closures
- array callables such as
[$object, 'method']or[ClassName::class, 'method']
Anything else throws DiException::invalidCallable(...).
Parameter resolution follows this order:
- If the parameter type is registered, return
get($type). - Otherwise, if the type names an instantiable class, return
create($type). - If the parameter type is
array, return the entire remaining$argsarray. - If manual
$argsstill contain values, shift and return the next one. - Otherwise return the parameter default value when available.
- Otherwise return
null.
This has a few practical consequences:
- registered class dependencies are shared
- unregistered instantiable class dependencies are created fresh
- scalar parameters are positional only
- missing required scalar parameters become
null, which can still fail later when PHP enforces the callable or constructor signature
Circular dependency protection
During get() and create(), the container tracks the abstract keys it is currently resolving.
If resolution re-enters an abstract that is already in progress, the package throws DiException::circularDependency(...) with a chain such as:
FooInterface -> BarService -> FooInterface
Reset behavior
resetContainer()clears cached instances and the in-progress resolution map.reset()clears both the registry bindings and the cached instances.
Use reset() only when you want a fully empty container state.
Important constraints
- The package only resolves constructor injection; there is no factory binding API in this package.
has()checks cached instances only. It does not tell you whether a dependency is registered.isRegistered()checks registry bindings only. It does not tell you whether an instance has been created.Di::__callStatic()throwsDiException::invalidCallable($method)when the current container does not implement the requested method.