Interface Binding
bind() makes an id resolve to a class:
$container->bind(LoggerInterface::class, FileLogger::class);
$container->bind(CacheInterface::class, RedisCache::class);
$container->get(LoggerInterface::class); // FileLoggerThe class is resolved like any other id — its own factory, its own binding, or auto-wiring — so a binding and a factory combine:
$container->bind(LoggerInterface::class, FileLogger::class);
$container->set(FileLogger::class, fn() => new FileLogger('/var/log/app.log'));The instance is shared between both ids:
$container->get(LoggerInterface::class) === $container->get(FileLogger::class); // trueAuto-wiring goes through the same bindings: a parameter typed LoggerInterface receives the FileLogger. Any id can be bound, not only an interface name: bind('logger', FileLogger::class).
Compatible Types
When the abstract is a class or interface name, the bound class must implement or extend it. Otherwise resolving the id throws ContainerException:
$container->bind(LoggerInterface::class, Mailer::class);
$container->get(LoggerInterface::class);
// ContainerException: Service 'LoggerInterface' is Mailer, not an instance of LoggerInterface.A binding to a class that does not exist throws ContainerException as well, naming the missing class.
Replacing a Registration
bind() replaces the id's factory or earlier binding and drops the instance cached for it, as set() does — the last registration of an id wins:
$container->set('logger', fn() => new NullLogger());
$container->bind('logger', FileLogger::class);
$container->get('logger'); // FileLoggerCycles
A class bound to itself, or two bindings pointing at each other, fail on get() with ContainerException:
$container->bind(ServiceA::class, ServiceB::class);
$container->bind(ServiceB::class, ServiceA::class);
$container->get(ServiceA::class);
// ContainerException: Circular dependency detected: ServiceA -> ServiceB -> ServiceA