Dependency Injection

Dependency Injection and Containers

With dependency injection, a class receives its collaborators through its constructor instead of building them, so a test can pass fakes. A container reads constructor types with Reflection and builds and shares each dependency.

Constructor injection and a small autowiring containerPHP
<?php
final class Container {
  private array $made = [];
  public function get(string $class): object {
    return $this->made[$class] ??= $this->autowire($class);         // one shared instance
  }
  private function autowire(string $class): object {                // read the constructor
    $params = new ReflectionClass($class)->getConstructor()?->getParameters() ?? [];
    return new $class(...array_map(fn(ReflectionParameter $p) => $p->isDefaultValueAvailable()
      ? $p->getDefaultValue() : $this->get($p->getType()->getName()), $params));
  }
}
final class Mailer { function send(string $to, string $m): void { echo "to $to: $m\n"; } }
final class Numbers { private int $n = 5001; function next(): int { return $this->n++; } }
final class Checkout {                                // asks for what it needs, builds nothing
  public function __construct(private Numbers $numbers, private Mailer $mailer) {}
  public function place(string $to): void {
    $this->mailer->send($to, "order #{$this->numbers->next()} confirmed");
  }
}
$c = new Container();
$c->get(Checkout::class)->place('ann@example.com');
$c->get(Checkout::class)->place('raj@example.com');
Output
to ann@example.com: order #5001 confirmed
to raj@example.com: order #5002 confirmed

The shared Numbers gave #5002. Real containers add bindings for interfaces and configuration. Call the container only at the entry point; a service locator hides dependencies again. PSR-11 standardizes get()/has(), PHP-DI 2,774 (https://github.com/PHP-DI/PHP-DI 2,774 ) (7.1.1, composer require php-di/php-di) adds bindings and a compiled container, and Laravel covers Laravel 2,157 's.