phpnomad / php-di-integration
PHP-DI integration for PHPNomad — compilable container, config repo, and registry backed by PHP-DI.
Requires
- php: >=8.2
- php-di/php-di: ^7.0
- phpnomad/config: ^1.0
- phpnomad/core: ^2.0 || ^3.0
- phpnomad/di: ^2.0
- phpnomad/loader: ^1.0 || ^2.0
- phpnomad/registry: ^1.0
Requires (Dev)
- mockery/mockery: ^1.5
- phpnomad/tests: ^0.3.0
- phpunit/phpunit: ^9.6
README
PHP-DI-backed implementations of PHPNomad's InstanceProvider/HasBindings,
ConfigStrategy, and Registry interfaces. An opt-in upgrade for consumers
that want compilable, fast resolution while preserving PHPNomad's runtime
extensibility.
Why
PHPNomad ships with an intentionally minimal default container. For distributed plugins (WordPress installs, etc.) where boot tax compounds across requests, this integration swaps in PHP-DI with build-time compilation — emitting a plain PHP class that opcache loads for free, eliminating per-request reflection cost.
The trade is a vendor dependency on php-di/php-di and a build-step that
emits the compiled container. The runtime API matches PHPNomad's existing
contract, so consumer code that already uses InstanceProvider,
HasBindings, ConfigStrategy, or the Registry traits doesn't change.
Installation
composer require phpnomad/php-di-integration
Usage
As a suite (most common)
Add the suite Initializer to your bootstrapper:
use PHPNomad\Integrations\PhpDi\Container\CompiledContainerLoader; use PHPNomad\Integrations\PhpDi\Initializer as PhpDiInitializer; use PHPNomad\Loader\Bootstrapper; $container = CompiledContainerLoader::load($tier); // tier identifies the compiled artifact (new Bootstrapper( $container, new PhpDiInitializer(), // Container + Config + Registry sub-initializers new YourCoreInitializer(), // ... ))->load();
CompiledContainerLoader::load($tier) returns a PhpDiContainer wrapping
either a build-time-compiled \DI\Container subclass (fast path) or a
runtime-built \DI\ContainerBuilder (fallback when no compiled artifact
is present). The runtime path is identical to non-compiled PHP-DI behavior.
Piecemeal composition
Power users that only want one of the three concerns can compose
sub-initializers directly. Container must come first since Config and
Registry depend on InstanceProvider:
use PHPNomad\Integrations\PhpDi\Container\Initializer as ContainerInitializer; use PHPNomad\Integrations\PhpDi\Config\Initializer as ConfigInitializer; (new Bootstrapper( $container, new ContainerInitializer(), new ConfigInitializer(), // skip Registry — keep PHPNomad's simple registry impl new YourCoreInitializer(), ))->load();
Build-time compilation
Wire Compiler::compileForTier() into your build step. Run it after
composer install (and after any vendor-prefixing step like Strauss)
so the compiled output references runtime class names correctly:
use PHPNomad\Integrations\PhpDi\Container\Compiler; $initializers = [ new \PHPNomad\Integrations\PhpDi\Initializer(), new YourCoreInitializer(), // ...recurse into Loaders to flatten sub-initializers... ]; Compiler::compileForTier( tier: 'lite', outDir: __DIR__ . '/build/lib/Compiled/PhpDiContainer', initializers: $initializers, );
This emits a class at <outDir>/Lite.php that the runtime loader will
prefer over the runtime-built container. Per-tier compilation is supported
— pass different tier identifiers and initializer sets for builds that
ship different binding configurations (e.g., free vs paid plugin tiers).
Boot order
Container must boot before Config and Registry, because the latter two
resolve through InstanceProvider. The suite Initializer encodes this;
piecemeal users must honor it manually.
Runtime overrides
The compiled container is not immutable. PHP-DI's set() against a built
container takes precedence over compiled bindings, so tier switches,
test bindings, and third-party extensions keep working at runtime.
Compiled containers reject Definition objects via set() at runtime —
the adapter's bind() and bindFactory() handle this transparently
(eager-resolving raw values when the underlying container is compiled).
Multi-abstract bind semantics
This integration mirrors PHPNomad's simple Container::bind() semantics
exactly: when you bind one concrete to multiple abstracts in a single
call, all abstracts share the resulting instance. Each abstract aliases
to the concrete independently — a later bind() that re-points one
abstract leaves the others pointing at the original concrete. Self-binding
(bind(Foo::class, Foo::class)) is special-cased to autowire directly
rather than alias to itself.
Requirements
- PHP 8.2+
- PHPNomad 2.x or 3.x
php-di/php-di^7.0
License
MIT