Introducción a Dependency Injection (DI) en PHP
El desarrollo moderno en PHP es imposible sin comprender los principios de bajo acoplamiento entre componentes. Dependency Injection (DI), o inyección de dependencias, es uno de los patrones de diseño clave que permite crear aplicaciones flexibles, testeables y fácilmente mantenibles. En lugar de que una clase cree sus propias dependencias (por ejemplo, un objeto de base de datos o un logger), las recibe desde el exterior. Esto cambia radicalmente la arquitectura del código.
En este artículo, analizaremos en detalle qué es Dependency Injection en PHP, cuáles son las formas de inyección, cómo funciona un contenedor DI y por qué este patrón se ha convertido en el estándar para frameworks modernos como Laravel, Symfony y Yii2.
1. El problema: Alto acoplamiento (Tight Coupling)
Antes de hablar de la solución, veamos el problema. Un ejemplo clásico es la clase UserController, que crea directamente un objeto Database dentro de sí misma:
<?phpclass Database { public function query(string $sql): array { // Conexión a la BD y ejecución de la consulta return []; }}
class UserController { private Database $db;
public function __construct() { // Dependencia rígida: creamos el objeto dentro del constructor $this->db = new Database(); }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}?>Desventajas de este enfoque:
- Dificultad para realizar pruebas: No se puede reemplazar
Databasepor un objeto mock en pruebas unitarias. - Violación del principio de responsabilidad única (SRP): La clase no solo se encarga de la lógica de negocio, sino también de crear sus dependencias.
- Dificultad de modificación: Si se desea cambiar MySQL por PostgreSQL, habría que modificar el código en todas las clases donde se crea
Database.
2. La solución: Inyección de dependencias (Dependency Injection)
Dependency Injection propone pasar las dependencias desde el exterior. Existen tres formas principales de inyección: a través del constructor, a través de setters y a través de una interfaz (método). Analicemos cada una.
2.1. Inyección por constructor (Constructor Injection)
Es la forma más común y recomendada. Las dependencias se pasan una sola vez al crear el objeto y no pueden modificarse posteriormente.
<?phpclass UserController { private Database $db;
// La dependencia se pasa desde el exterior public function __construct(Database $db) { $this->db = $db; }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}
// Uso:$db = new Database();$controller = new UserController($db);?>Ventajas: Garantiza que la dependencia siempre esté disponible; es ideal para dependencias obligatorias. Desventajas: Si la clase tiene muchas dependencias, el constructor se vuelve voluminoso.
2.2. Inyección por setters (Setter Injection)
Es adecuada para dependencias opcionales que pueden establecerse después de crear el objeto.
<?phpclass Logger { public function log(string $message): void { echo $message; }}
class UserController { private ?Logger $logger = null;
public function setLogger(Logger $logger): void { $this->logger = $logger; }
public function getUsers(): array { if ($this->logger) { $this->logger->log('Obteniendo usuarios...'); } // Lógica para obtener usuarios return []; }}
// Uso:$controller = new UserController();$controller->setLogger(new Logger());?>Ventajas: