Container
Foundation Container provides a shared container contract and service provider base class. Use it to describe how application services are constructed while keeping dependency resolution and the underlying container implementation out of the services themselves.
Installation
Section titled “Installation”Install the split package in applications that define their own container or service providers:
Other Foundation packages install Container automatically when they depend on it. Composer does not require a second explicit installation in that case.
Prepare the application
Section titled “Prepare the application”Create one container in the application composition root and register providers in dependency order. These guides establish that structure:
Let the container autowire concrete classes
Section titled “Let the container autowire concrete classes”The container can construct an unbound concrete class when its constructor dependencies are also concrete classes:
Resolve the application entrypoint where it is needed:
Prefer constructor injection throughout application code. Calling get() inside a service hides its dependencies and turns the container into a service locator.
Select an interface implementation
Section titled “Select an interface implementation”In src/Catalog/Catalog_Provider.php, bind an interface when the container cannot infer which implementation the application wants. Use bind() for a new instance on each resolution and singleton() when every resolution should return the same instance:
Bindings are lazy. Registering Remote_Catalog does not construct it; the container builds it when another service first requests Catalog.
Every provider receives the shared container and read-only configuration snapshot. Use $this->config when a feature needs configuration; providers that do not need it can simply ignore it. This keeps one provider shape throughout the application instead of requiring developers to choose a base class.
Foundation providers register eagerly. Keep expensive services lazy by binding them in register() and letting the container construct them on first use; do not add provider-level deferred or boot phases.
Supply configuration and scalar values
Section titled “Supply configuration and scalar values”In the same src/Catalog/Catalog_Provider.php, use a contextual binding when one class needs a scalar or a feature-specific implementation. Target scalar constructor arguments by their $name. Import Foundation’s Resolver as C when a factory callback must resolve another service:
The callback aliases Catalog to the configured Remote_Catalog singleton. This preserves the contextual bindings registered for the concrete class and ensures both identifiers resolve the same object.
Factory callbacks receive Foundation’s Resolver contract. Application providers should not type-hint DI52 directly; keeping the callback behind the Foundation contract allows the underlying container integration to change without requiring edits throughout application providers.
Use a factory callback only when the value must be computed or fetched from the container. Let the container construct the complete service whenever it can.
Build a collection across providers
Section titled “Build a collection across providers”In src/Report/Report_Provider.php, use mergeArrayVar() when independent providers contribute to one ordered collection. The provider that owns the collection registers its default and supplies it to the consuming class:
Other feature providers append their implementations without replacing earlier contributions. For example, src/Report/Csv/Csv_Provider.php can contribute the CSV implementation:
The registration guard keeps each provider’s contribution unchanged if it is registered again.
Register lazy WordPress callbacks
Section titled “Register lazy WordPress callbacks”In src/Catalog/Catalog_Provider.php, use callback() to let WordPress resolve a service only when its hook runs:
This avoids constructing the synchronizer during every request merely to register its callback.
Handle container failures
Section titled “Handle container failures”Catch StellarWP\Foundation\Container\Exceptions\NotFoundException when a requested identifier may be absent. Failures raised by the container while registering or resolving services use StellarWP\Foundation\Container\Exceptions\ContainerException. Both implement the corresponding PSR container exception interfaces, and the original application failure remains available through getPrevious() when the underlying container wrapped one. Exceptions thrown by a provider’s own register() method remain that provider’s exception and propagate unchanged.
Decorate a service
Section titled “Decorate a service”In src/Catalog/Catalog_Provider.php, use a decorator chain when cross-cutting behavior should wrap a service without changing its implementation. List the outermost decorator first and the base implementation last:
Resolving Catalog returns one Logging_Catalog that wraps Caching_Catalog, which wraps Remote_Catalog. Use bindDecorators() instead when the application needs a new chain on every resolution.
Testing
Section titled “Testing”Replace an implementation in a focused test
Section titled “Replace an implementation in a focused test”Bind a test double to the same contract before resolving the class under test:
Test application services through their public behavior. Reserve container integration tests for provider graphs where the binding itself is the behavior under test.
Upgrading to 2.0
Section titled “Upgrading to 2.0”Most existing providers need little or no structural change. A provider that already extends Provider, declares no constructor, and registers its feature in register() keeps the same shape.
First, update the application composition root to create the shared container with ContainerFactory. This replaces direct ContainerAdapter, DI52, and Adbar\Dot setup:
Then update each application provider:
- Keep extending
StellarWP\Foundation\Container\Contracts\Providerand implementregister(). - Remove custom provider constructors. Read configuration through
$this->configand let the base provider receive the container. - Type factory callbacks against
StellarWP\Foundation\Container\Contracts\Resolver, conventionally imported asC, instead of DI52’s container.
After those changes, src/Catalog/Catalog_Provider.php can look like this:
If the application used lower-level container APIs, make these additional replacements:
- Replace direct implementations of the removed
Providableinterface with classes that extendProvider. - Move provider
boot(),provides(), and deferred-provider behavior intoregister()or an application-owned lifecycle. - Replace
ContainerAdapter::getContainer()and forwarded DI52 methods with operations declared by Foundation’sContainerandResolvercontracts. - Catch Foundation’s
ContainerExceptionorNotFoundExceptioninstead of DI52 exceptions.