PSR-11 DI container with contextual bindings, tags, and attributes.
- PHP 8.2+
-
psr/container^2.0
composer require mb/containeruse 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);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));Override the type-hinted dependency with #[Inject]:
use MB\Container\Attributes\Inject;
class MyService
{
public function __construct(
#[Inject(FileLogger::class)]
LoggerInterface $logger
) {}
}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.
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]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
});$container->bind(FileLogger::class, FileLogger::class);
$container->alias(FileLogger::class, 'logger');
$logger = $container->make('logger');
$container->has('logger'); // trueRun 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
});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 factoryYou 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().
-
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).
A small script measures resolve time for a dependency graph (interfaces and services) with and without compilation:
php benchmarks/container_benchmark.phpIt 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.phpMIT.