¿Qué es un contenedor DI?
Un contenedor de inyección de dependencias (DIC) es una clase que puede instanciar y configurar objetos.
Puede que le sorprenda, pero en muchos casos no necesita un contenedor de inyección de dependencias para aprovechar los beneficios de la inyección de dependencias (DI para abreviar). Después de todo, incluso en el capítulo introductorio, mostramos DI con ejemplos concretos y no se necesitó ningún contenedor.
Sin embargo, si necesita administrar una gran cantidad de objetos diferentes con muchas dependencias, un contenedor de inyección de dependencias será realmente útil. Este es el caso, por ejemplo, de las aplicaciones web construidas sobre un framework.
En el capítulo anterior, presentamos las clases Article
y UserController
. Ambas tienen algunas
dependencias, a saber, la base de datos y la fábrica ArticleFactory
. Y ahora crearemos un contenedor para estas
clases. Por supuesto, para un ejemplo tan simple, no tiene sentido tener un contenedor. Pero lo crearemos para mostrar cómo se ve
y funciona.
Aquí hay un contenedor simple codificado para el ejemplo dado:
class Container
{
public function createDatabase(): Nette\Database\Connection
{
return new Nette\Database\Connection('mysql:', 'root', '***');
}
public function createArticleFactory(): ArticleFactory
{
return new ArticleFactory($this->createDatabase());
}
public function createUserController(): UserController
{
return new UserController($this->createArticleFactory());
}
}
El uso se vería así:
$container = new Container;
$controller = $container->createUserController();
Simplemente le pedimos al contenedor el objeto y ya no necesitamos saber nada sobre cómo crearlo y cuáles son sus dependencias; el contenedor sabe todo eso. Las dependencias son inyectadas automáticamente por el contenedor. Ahí radica su poder.
Hasta ahora, el contenedor tiene todos los datos codificados. Así que daremos el siguiente paso y agregaremos parámetros para que el contenedor sea realmente útil:
class Container
{
public function __construct(
private array $parameters,
) {
}
public function createDatabase(): Nette\Database\Connection
{
return new Nette\Database\Connection(
$this->parameters['db.dsn'],
$this->parameters['db.user'],
$this->parameters['db.password'],
);
}
// ...
}
$container = new Container([
'db.dsn' => 'mysql:',
'db.user' => 'root',
'db.password' => '***',
]);
Los lectores atentos pueden haber notado un cierto problema. Cada vez que obtengo un objeto UserController
,
también se crea una nueva instancia de ArticleFactory
y la base de datos. Definitivamente no queremos eso.
Por lo tanto, agregaremos un método getService()
que devolverá las mismas instancias siempre:
class Container
{
private array $services = [];
public function __construct(
private array $parameters,
) {
}
public function getService(string $name): object
{
if (!isset($this->services[$name])) {
// getService('Database') llamará a createDatabase()
$method = 'create' . $name;
$this->services[$name] = $this->$method();
}
return $this->services[$name];
}
// ...
}
En la primera llamada, por ejemplo, $container->getService('Database')
, hará que createDatabase()
cree el objeto de la base de datos, lo almacenará en el array $services
y lo devolverá directamente en la próxima
llamada.
También modificaremos el resto del contenedor para usar getService()
:
class Container
{
// ...
public function createArticleFactory(): ArticleFactory
{
return new ArticleFactory($this->getService('Database'));
}
public function createUserController(): UserController
{
return new UserController($this->getService('ArticleFactory'));
}
}
Por cierto, el término servicio se refiere a cualquier objeto administrado por el contenedor. Por eso el nombre del método
getService()
.
Hecho. ¡Tenemos un contenedor DI completamente funcional! Y podemos usarlo:
$container = new Container([
'db.dsn' => 'mysql:',
'db.user' => 'root',
'db.password' => '***',
]);
$controller = $container->getService('UserController');
$database = $container->getService('Database');
Como puede ver, escribir un DIC no es nada complicado. Vale la pena recordar que los propios objetos no saben que algún contenedor los está creando. Por lo tanto, es posible crear cualquier objeto en PHP de esta manera sin interferir con su código fuente.
Crear y mantener manualmente una clase de contenedor puede convertirse rápidamente en una pesadilla. Por lo tanto, en el próximo capítulo, hablaremos sobre el Contenedor Nette DI, que puede generarse y actualizarse casi por sí mismo.