IoC Container
Also Known As
TL;DR
Explanation
An IoC (Inversion of Control) container, also called a DI (Dependency Injection) container or service container, maintains a registry of how to construct objects. When you request a class, the container inspects its constructor type hints, recursively resolves each dependency, and returns a fully-constructed instance. In Laravel, the service container is the central registry — type-hint an interface in a controller constructor and the container injects the concrete implementation bound to that interface. Symfony's DI component reads service definitions from YAML/XML/PHP configuration. Binding patterns: concrete bindings (App::bind(UserRepository::class, EloquentUserRepository::class)); singleton bindings (App::singleton(Cache::class, RedisCache::class) — same instance reused); factory bindings (App::bind(Report::class, fn($app) => new Report($app->make(DB::class), config('reports')))); contextual bindings (inject different implementations depending on which class is requesting the dependency).
Common Misconception
Why It Matters
Common Mistakes
- Injecting the container itself into classes — this is the Service Locator antipattern; inject specific dependencies, not the container.
- Binding concrete classes to concrete classes — bind interfaces to implementations so you can swap implementations without changing dependent code.
- Not using singleton binding for expensive-to-construct services — database connections and HTTP clients should be shared instances, not recreated per-request.
- Overusing the container for simple value objects — inject config values directly, not through the container.
Code Examples
// Service locator antipattern — container injected into class
class OrderService {
public function __construct(private Container $container) {}
public function process(int $id): void {
// Hidden dependency — unclear what this class needs
$repo = $this->container->make(OrderRepository::class);
$mail = $this->container->make(Mailer::class);
}
}
// Constructor injection — dependencies explicit, testable
class OrderService {
public function __construct(
private OrderRepository $orders,
private Mailer $mailer,
) {}
public function process(int $id): void {
$order = $this->orders->find($id); // injected dependency
$this->mailer->send(new OrderConfirmed($order));
}
}
// ServiceProvider — bind interface to implementation once
public function register(): void {
$this->app->singleton(OrderRepository::class, EloquentOrderRepository::class);
$this->app->singleton(Mailer::class, SmtpMailer::class);
}