mb4it / container
PSR-11 DI container with contextual bindings, tags, and attributes
Requires
- php: ^8.2
- psr/container: ^2.0.0
Requires (Dev)
- illuminate/container: ^12.00
- phpunit/phpunit: ^10.0
- symfony/dependency-injection: ^8.0
This package is auto-updated.
Last update: 2026-02-15 17:13:39 UTC
README
PSR-11 DI container with contextual bindings, tags, and attributes.
Requirements
- PHP 8.2+
psr/container^2.0
Installation
composer require mb/container
Basic usage
use MB\Container\Container; $container = new Container(); // Bind interface to implementation $container->bind(LoggerInterface::class, FileLogger::class); // Resolve $logger = $container->get(LoggerInterface::class); // Singleton $container->singleton(Cache::class, RedisCache::class); $cache = $container->make(Cache::class);
Contextual bindings
Inject different implementations depending on the class that is being built:
$container->bind(LoggerInterface::class, NullLogger::class); $container->when(ServiceA::class) ->needs(LoggerInterface::class) ->give(FileLogger::class); // ServiceA receives FileLogger; other classes receive NullLogger $service = $container->make(ServiceA::class);
You can also use a callable:
$container->when(ServiceA::class) ->needs(LoggerInterface::class) ->give(fn (Container $c) => $c->make(FileLogger::class));
Attribute injection
Override the type-hinted dependency with #[Inject]:
use MB\Container\Attributes\Inject; class MyService { public function __construct( #[Inject(FileLogger::class)] LoggerInterface $logger ) {} }
Class attributes
You can declare alias, tag, and singleton on a class and register it in one call with registerClass():
#[Alias(string $alias)]— register the class under an additional name (e.g.$container->make('logger')).#[Tag(string $tag)]— add the class to a tag (multiple#[Tag(...)]allowed).#[Singleton]— register as shared (one instance per container).
use MB\Container\Container; use MB\Container\Attributes\Alias; use MB\Container\Attributes\Tag; use MB\Container\Attributes\Singleton; #[Singleton] #[Alias('logger')] #[Tag('handlers')] class FileLogger implements LoggerInterface {} $container = new Container(); $container->registerClass(FileLogger::class); // Same as: $container->singleton(FileLogger::class, FileLogger::class); // $container->alias(FileLogger::class, 'logger'); // $container->tag([FileLogger::class], 'handlers'); $container->make('logger'); // FileLogger instance $container->tagged('handlers'); // [FileLogger instance]
Use registerClass() when you prefer to keep registration metadata on the class instead of repeating bind / alias / tag calls.
Tags and tagged resolution
Tag multiple bindings and resolve them as a list:
$container->bind(FileLogger::class, FileLogger::class); $container->bind(NullLogger::class, NullLogger::class); $container->tag([FileLogger::class, NullLogger::class], 'loggers'); $loggers = $container->tagged('loggers'); // [FileLogger instance, NullLogger instance]
Extending and events
Decorate or observe resolutions:
$container->extend(LoggerInterface::class, function (object $instance, Container $c) { return new DecoratingLogger($instance); }); $container->resolving(LoggerInterface::class, function (object $instance) { // called when resolving }); $container->afterResolving(LoggerInterface::class, function (object $instance) { // called after resolution });
Aliases
$container->bind(FileLogger::class, FileLogger::class); $container->alias(FileLogger::class, 'logger'); $logger = $container->make('logger'); $container->has('logger'); // true
Rebinding
Run a callback when a binding is replaced (e.g. by bind() or instance()):
$container->rebinding(LoggerInterface::class, function (Container $c, object $instance) { // previous instance when binding changed });
Compilation
Compile bindings into factories for faster resolution (e.g. in production):
$container->bind(LoggerInterface::class, FileLogger::class); $container->bind(ServiceA::class, ServiceA::class); $container->compile(); $service = $container->make(ServiceA::class); // uses compiled factory
You can keep calling bind(), instance(), or when()->give() after compile(). The compiled factory for the affected abstract (or concrete, for contextual rules) is then invalidated, so the next make() uses the current bindings. To re-optimize everything after configuration changes, call compile() again.
For maximum resolution speed (Symfony-like), use compileToFile(string $path): the container generates a PHP class file and uses it for resolution. Bindings changed after compileToFile() are invalidated for that abstract; the next make() uses the normal path for them. Call compileToFile($path) again to regenerate after configuration changes.
You can also inject a pre-filled CompiledRepository via the constructor or setCompiledRepository().
PSR-11
get(string $id): mixed— resolves the entry; throwsNotFoundExceptionInterfaceif not found.has(string $id): bool— returns whether the container can resolve the id (aliases are resolved).
Benchmark
A small script measures resolve time for a dependency graph (interfaces and services) with and without compilation:
php benchmarks/container_benchmark.php
It reports time in ms and resolves per second for MB container (with/without compile) and, when installed, for Laravel and Symfony:
composer require --dev illuminate/container # optional: Laravel composer require --dev symfony/dependency-injection # optional: Symfony php benchmarks/container_benchmark.php
License
MIT.