PHP, la manera correcta

6.2. Usos avanzados de la inyección de dependencias

Si has leído artículos o libros sobre la inyección de dependencias, entonces es posible que te hayas encontrado con los términos: Inversión de Control o Principio de Inversión de Dependencias. Estos son precisamente los problemas avanzados que la inyección de dependencias trata de resolver.

6.2.1. Inversión de control

La inversión de control es un mecanismo que permite invertir la forma en la que se controla un sistema de software haciendo que este control esté completamente separado de los objetos controlados.

Si hablamos de inyección de dependencias, entonces la inversión de control consiste en desacoplar las dependencias para controlarlas e instanciarlas de manera separada al código que las utiliza.

Los mejores frameworks PHP llevan años tratando de conseguir la inversión de control. No obstante, la pregunta es qué parte del control se está invirtiendo y a dónde se está llevando ese control. Así por ejemplo, los frameworks MVC normalmente contienen un super objeto o controlador base del que tu código debe heredar para poder acceder a sus dependencias. En esto consiste la inversión de control, ya que las dependencias simplemente se han movido de lugar.

La inyección de dependencias permite resolver este problema de una manera más elegante inyectando solamente las dependencias que se necesitan y solamente cuando se necesitan.

6.2.2. El principio de inversión de dependencias

El principio de inversión de dependencias corresponde a la letra D del acrónimo S.O.L.I.D. que resume algunos de los principios utilizados para desarrollar buenas aplicaciones orientadas a objetos. En concreto, este principio indica que el código "debería depender de abstracciones, no de concreciones".

En otras palabras, este principio indica que las dependencias deberían ser interfaces/contratos o clases abstractas, en vez de implementaciones concretas. Así que podemos refactorizar el código del ejemplo anterior de la siguiente manera:

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;
    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

Ahora la clase Database depende de una interfaz, no de una clase concreta. Este cambio trae consigo varias ventajas.

Imagina que estás trabajando en una misma aplicación con otros programadores y que el adaptador es responsabilidad de otro programador. Si utilizamos el primer código que se mostró al principio de este capítulo, deberíamos esperar a que ese otro programador termine su trabajo antes de poder crear un mock de ese adaptador en nuestros tests.

Como ahora la dependencia es una interfaz, podemos crear el mock de la interfaz tranquilamente sin depender del trabajo de otros programadores. En otras palabras, al trabajar con una interfaz, los dos podemos trabajar en diferentes partes del sistema a la vez y con la garantía de que nuestro código va a funcionar correctamente.

Otra de las ventajas de hacerlo así es que ahora el código es mucho más escalable. Si dentro de un año por ejemplo decidimos migrar a un tipo de base de datos diferente, podemos crear un nuevo adaptador que implemente la misma interfaz y pasar a la clase Database este nuevo adaptador. Como estamos utilizando la misma interfaz, el resto del código seguiría funcionando bien sin necesidad de hacer ningún otro cambio.