Contenedor de dependencias con php
Un contenedor de dependencias, o Dependency Injection Container, es una herramienta o clase especial que se encarga de:- Crear (instanciar) los objetos de tu aplicación.
- Resolver automáticamente todas las dependencias que esos objetos necesitan (es decir, pasarles por el constructor o setters lo que requieren).
- Gestionar cómo y cuándo se crean esos objetos (¿siempre uno nuevo? ¿el mismo para toda la app? ¿solo cuando se necesita realmente?).
Inyección de Dependencias vs Contenedor de Dependencias
- Inyección de Dependencias: Es un patrón de diseño. Consiste en no crear las dependencias dentro de una clase, sino recibirlas desde fuera (por constructor, setter, interfaz, etc.) .Ejemplo básico sin contenedor:
class UserService { private $logger; public function __construct(Logger $logger) { // ← inyección por constructor $this->logger = $logger; } } - Contenedor de dependencias: Es la herramienta que hace la inyección automáticamente por ti. Se le dices (o configura automáticamente) qué clases existen y cómo se relacionan, y el contenedor se encarga del resto.
logger = $logger; } public function send(string $to, string $subject) { $this->logger->info("Enviando email a $to: $subject"); // envío real... } } // Esta clase necesita un Logger para funcionar, por eso lo recibe en el constructor (inyección de dependencias). //Esta clase necesita un EmailService → también lo recibe por constructor. class UserService { private $emailService; public function __construct(EmailService $emailService) { $this->emailService = $emailService; } public function register(string $email) { $this->emailService->send($email, "Bienvenido!"); } } // ------------------- Contenedor muy básico ------------------- //$services es un array donde guardamos fábricas (funciones que saben cómo crear cada //objeto) //La clave ($id) es un string que identifica el servicio (ej: 'logger', 'email.service') //El valor es una función (callable) que cuando se ejecute, crea la instancia class Container { private array $services = []; public function set(string $id, callable $factory): void { $this->services[$id] = $factory; } public function get(string $id) { if (!isset($this->services[$id])) { throw new Exception("Servicio '$id' no encontrado"); } // Lazy loading + singleton simple static $instances = []; if (!isset($instances[$id])) { $instances[$id] = $this->services[$id]($this); } return $instances[$id]; } } //Cómo se configuran / registran los servicios $container = new Container(); $container->set('logger', fn() => new Logger()); $container->set('email.service', fn($c) => new EmailService($c->get('logger'))); $container->set('user.service', fn($c) => new UserService($c->get('email.service'))); // En tu aplicación (controlador, comando, etc) //Uso real en la aplicación $userService = $container->get('user.service'); $userService->register('juan@example.com');
Ventajas principales de usar un contenedor
- Código mucho más limpio y sin "new" por todos lados.
- Muy fácil cambiar implementaciones (ej: cambiar de FileLogger a DatabaseLogger sin tocar el resto del código).
- Testing súper sencillo (inyectas mocks/fakes fácilmente).
- Gestión automática de scopes (singleton, por-request, prototype/transient...).
- Reduce el acoplamiento fuerte entre clases.
- La app se vuelve más modular y mantenible.
Los contenedores más populares
| Framework / Librería | Contenedor propio | Nivel de autowiring (automático) | Uso típico |
|---|---|---|---|
| Laravel | Illuminate\Container | Excelente | Proyectos Laravel |
| Symfony | symfony/dependency-injection | Muy bueno | Proyectos Symfony |
| PHP-DI | php-di/php-di | Uno de los mejores | Cualquier proyecto |
| Laminas / Mezzio | laminas/laminas-servicemanager | Bueno | Apps enterprise |
| Yii 2/3 | Contenedor integrado | Bueno | Proyectos Yii |