1. Code
  2. PHP

Inyección de dependencias: ¿eh?

Lo más probable es que, en algún momento de tu aprendizaje, te hayas encontrado con el término, "inyección de dependencias". Si aún estás empezando a aprender, es probable que hayas estado confundido y te saltaras esa parte. Aún así, este es un aspecto importante de la escritura de código mantenible (y comprobable). En este artículo, lo explicaré de la manera más simple que pueda.
Scroll to top
7 min read

Spanish (Español) translation by Andrea Jiménez (you can also view the original English article)

Lo más probable es que, en algún momento de tu aprendizaje, te hayas encontrado con el término, "inyección de dependencias". Si aún estás empezando a aprender, es probable que hayas estado confundido y te saltaras esa parte. Aún así, este es un aspecto importante de la escritura de código mantenible (y comprobable). En este artículo, lo explicaré de la manera más simple que pueda.


Un ejemplo

Pasemos a un código bastante genérico y analicemos sus defectos.

1
class Photo {
2
	/**

3
	 * @var PDO The connection to the database

4
	 */
5
	protected $db;
6
7
	/**

8
	 * Construct.

9
	 */
10
	public function __construct()
11
	{
12
		$this->db = DB::getInstance();
13
	}
14
}

A primera vista, este código puede parecer bastante inofensivo. Pero ten en cuenta el hecho de que, ya, codificamos una dependencia: la conexión de la base de datos. ¿Qué pasa si queremos añadir una capa de persistencia diferente? O, piénsalo de esta manera: ¿por qué el objeto Photo debería comunicarse con fuentes externas? ¿Eso no viola el concepto de separación de inquietudes? Desde luego que sí. Este objeto no debería estar relacionado con nada que no esté directamente relacionado con el objeto Photo.

La idea principal es que tus clases deben ser responsables de una sola cosa. Con esto en mente, no debería ser responsable de conectarse a bases de datos y otras cosas de esa naturaleza.

Piensa en los objetos de la misma manera que piensas en tu mascota. El perro no decide cuándo sale a pasear o a jugar al parque. ¡Tú sí! No le corresponde tomar estas decisiones.

Recuperemos el control de la clase y, en cambio, pasemos la conexión a la base de datos. Hay dos formas de lograr esto: inyección por constructor y por setter, respectivamente. Estos son los ejemplos de ambos:

Inyección por constructor

1
class Photo {
2
	/**

3
	 * @var PDO The connection to the database

4
	 */
5
	protected $db;
6
7
	/**

8
	 * Construct.

9
	 * @param PDO $db_conn The database connection

10
	 */
11
	public function __construct($dbConn)
12
	{
13
		$this->db = $dbConn;
14
	}
15
}
16
17
$photo = new Photo($dbConn);

Arriba, estamos inyectando todas las dependencias requeridas, cuando se ejecuta el método constructor de la clase, en lugar de codificarlas directamente en la clase.

Inyección por setter

1
class Photo {
2
	/**

3
	 * @var PDO The connection to the database

4
	 */
5
	protected $db;
6
7
	public function __construct() {}
8
9
	/**

10
	 * Sets the database connection

11
	 * @param PDO $dbConn The connection to the database.

12
	 */
13
	public function setDB($dbConn)
14
	{
15
		$this->db = $dbConn;
16
	}
17
}
18
19
$photo = new Photo;
20
$photo->setDB($dbConn);

Con este simple cambio, la clase ya no depende de ninguna conexión específica. El sistema exterior conserva el control completo, como debe ser el caso. Aunque puede que no sea visible inmediatamente, esta técnica también hace que la clase sea considerablemente más fácil de probar, ya que ahora podemos simular la base de datos, al llamar al método setDB.

Aún mejor, si más tarde decidimos utilizar una forma diferente de persistencia, gracias a la inyección de dependencia, es muy fácil.

"La inyección de dependencia es donde los componentes reciben sus dependencias a través de sus constructores, métodos o directamente en los campos".


El problema

Hay un problema con el uso de la inyección por setter de esta manera: hace que la clase sea considerablemente más difícil de trabajar. El usuario ahora debe ser plenamente consciente de las dependencias de la clase y debe recordar configurarlas en consecuencia. Ten en cuenta, cuando nuestra clase ficticia requiera un par de dependencias más al final de la línea. Bueno, siguiendo las reglas del patrón de inyección de dependencias, tendríamos que hacer esto:

1
$photo = new Photo;
2
$photo->setDB($dbConn);
3
$photo->setConfig($config);
4
$photo->setResponse($response);

¡Ay! la clase puede ser más modular, pero también acumulamos confusión y complejidad. Antes, el usuario podía simplemente crear una nueva instancia de Photo, pero ahora tiene que acordarse de configurar todas estas dependencias. ¡Qué dolor!


La solución

La solución a este dilema es crear una clase contenedora que se encargue de la mayor parte del trabajo por nosotros. Si alguna vez te encontraste con el término, "Inversión de Control (IoC)", ahora sabes a lo que se refieren.

Definición: En ingeniería de software, Inversión de control (IoC) es una práctica de programación orientada a objetos donde el acoplamiento de objetos está enlazado en tiempo de ejecución por un objeto ensamblador y normalmente no se conoce en tiempo de compilación mediante análisis estático.

Esta clase almacenará un registro de todas las dependencias de nuestro proyecto. Cada clave tendrá una función lambda asociada que crea una instancia de la clase asociada.

Hay un par de maneras de resolver esto. Podríamos ser explícitos y almacenar métodos, como newPhoto:

1
// Also frequently called "Container"

2
class IoC {
3
	/**

4
	 * @var PDO The connection to the database

5
	 */
6
	protected $db;
7
8
	/**

9
	 * Create a new instance of Photo and set dependencies.

10
	 */
11
	public static newPhoto()
12
	{
13
		$photo = new Photo;
14
		$photo->setDB(static::$db);
15
		// $photo->setConfig();

16
		// $photo->setResponse();

17
18
		return $photo;
19
	}
20
}
21
22
$photo = IoC::newPhoto();

Ahora, $photo será igual a una nueva instancia de la clase Photo, con todas las dependencias necesarias establecidas. De esta manera, el usuario no tiene que acordarse de configurar estas dependencias manualmente; simplemente llama al método newPhoto.

La segunda opción, en lugar de crear un nuevo método para cada instancia de clase, es escribir un contenedor de registro genérico, así:

1
class IoC {
2
	/**

3
	 * @var PDO The connection to the database

4
	 */
5
	protected static $registry = array();
6
7
	/**

8
	 * Add a new resolver to the registry array.

9
	 * @param  string $name The id

10
	 * @param  object $resolve Closure that creates instance

11
	 * @return void

12
	 */
13
	public static function register($name, Closure $resolve)
14
	{
15
		static::$registry[$name] = $resolve;
16
	}
17
18
	/**

19
	 * Create the instance

20
	 * @param  string $name The id

21
	 * @return mixed

22
	 */
23
	public static function resolve($name)
24
	{
25
		if ( static::registered($name) )
26
		{
27
			$name = static::$registry[$name];
28
			return $name();
29
		}
30
31
		throw new Exception('Nothing registered with that name, fool.');
32
	}
33
34
	/**

35
	 * Determine whether the id is registered

36
	 * @param  string $name The id

37
	 * @return bool Whether to id exists or not

38
	 */
39
	public static function registered($name)
40
	{
41
		return array_key_exists($name, static::$registry);
42
	}
43
}

No dejes que este código te asuste; es realmente muy simple. Cuando el usuario llama al método IoC::register, está agregando un identificador, como photo, y su solucionador asociado, que es solo una expresión lambda que crea la instancia y configura las dependencias necesarias en la clase.

Ahora, podemos registrar y resolver dependencias a través de la clase IoC, así:

1
// Add `photo` to the registry array, along with a resolver

2
IoC::register('photo', function() {
3
	$photo = new Photo;
4
	$photo->setDB('...');
5
	$photo->setConfig('...');
6
7
	return $photo;
8
});
9
10
// Fetch new photo instance with dependencies set

11
$photo = IoC::resolve('photo');

Por lo tanto, podemos observar que, con este patrón, no estamos creando manualmente una instancia de la clase. En su lugar, lo registramos con el contenedor de IoC y luego solicitamos una instancia específica. Esto reduce la complejidad que introdujimos cuando eliminamos las dependencias codificadas de la clase.

1
// Before

2
$photo = new Photo;
3
4
// After

5
$photo = IoC::resolve('photo');

Prácticamente la misma cantidad de caracteres, pero ahora la clase es significativamente más flexible y comprobable. En el uso del mundo real, es probable que quieras extender esta clase para permitir la creación de singletons también.


Adopción de métodos mágicos

Si queremos reducir aún más la longitud de la clase IoC, podemos aprovechar los métodos mágicos, es decir, __set() y __get(), que se activarán si el usuario llama a un método que no existe en la clase.

1
class IoC {
2
	protected $registry = array();
3
4
	public function __set($name, $resolver)
5
	{
6
		$this->registry[$name] = $resolver;
7
	}
8
9
	public function __get($name)
10
	{
11
		return $this->registry[$name]();
12
	}
13
}

Popularizado por Fabien Potencier, se trata de una implementación súper mínima, pero funcionará. El si se ejecuta o no __get() o set() dependerá de si el usuario está estableciendo un valor o no.

El uso principal sería:

1
$c = new IoC;
2
$c->mailer = function() {
3
  $m = new Mailer;
4
  // create new instance of mailer

5
  // set creds, etc.

6
  
7
  return $m;
8
};
9
10
// Fetch, boy

11
$mailer = $c->mailer; // mailer instance

¡Gracias por leer!