mb4it/container

PSR-11 DI container with contextual bindings, tags, and attributes

Maintainers

Package info

github.com/Dictator90/mb-container

pkg:composer/mb4it/container

Statistics

Installs: 6

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-02-15 17:12 UTC

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; throws NotFoundExceptionInterface if 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.