Domina la inyección de dependencias de Laravel

Spread the love

Los frameworks de desarrollo se han vuelto populares por la facilidad con la que puedes crear prototipos y aplicaciones con componentes poco acoplados. Para lograr ese bajo acoplamiento es muy común que los framework como Laravel utilicen una estrategia que se conoce como inyección de dependencias.

En este articulo exploraremos como implementa esta técnica Laravel.

Qué es la inyección de dependencias

La inyección de dependencias es un principio que no pertenece a un lenguaje o framework en particular, así que es común encontrar implementaciones de este principio en frameworks de distintos lenguajes. La idea detrás de la inyección de dependencias es que las clases o componentes de un sistema no deben de configurar sus dependencias de forma estática sino que deben de ser configuradas desde el exterior.

Para profundizar en el tema me valdré de un ejemplo muy sencillo pero que nos permitirá comprender el problema que resuelve la inyección de dependencias y de esa forma crear una definición.

Vamos a suponer que en nuestro sistema tenemos una clase llamada GenericClass que requiere escribir en una bitácora y para ello utiliza una clase diseñada para este propósito llamada Logger. La forma más convencional de expresar este sencillo modelo es mediante una asociación como lo muestra la figura 1.

Dependencia de clases
Figura 1

Este modelo se puede expresar mediante código como sigue:

class GenericClass
{
    /**
     * @var Logger
     */
    private $logger;

    public function __construct()
    {
        $this->logger = new Logger;
    }
}

El problema con este diseño es que es poco flexible ya que si hacemos cambios en Logger tal vez también tengamos que hacer cambios en GenericClass. A este efecto se le llama alto acoplamiento.

Algunos de los problemas que puedes tener con este diseño son los siguientes.

  • Cambiar o actualizar Logger implica cambios en GenericClass.
  • La dependencia con Logger debe de estar disponible en tiempo de ejecución.
  • GenericClass tiene la responsabilidad de crear una instancia de Logger.
  • No se puede probar de forma aislada GenericClass.

La forma en la que se pueden resolver estos problemas, implica que GenericClass, en lugar de depender directamente de las implementaciones de Logger, solo dependa de una abstracción que puede implementar Logger. Este cambio se muestra en la figura 2.

Inyección de dependencias a interfaz
Figura 2

Este nuevo modelo lo podemos expresar mediante código de la siguiente forma

class GenericClass
{
    /**
     * @var Logger
     */
    private $logger;

    public function __construct(LoggerInterface $looger)
    {
        $this->logger = $logger;
    }
}

Ahora GenericClass solo depende de una interfaz, esto nos permite reducir el acoplamiento entre clases y darle flexibilidad a nuestro diseño. De esta forma podemos cambiar el comportamiento de Logger sin necesidad de cambiar completamente GenericClass.

En la práctica este diseño requiere que GenericClass ya no tenga el control para crear una instancia de Logger sino que se indica en la declaración de la clase, lo que obliga a que la creación de Logger esté fuera de GenericClass. Esto es lo que se conoce como inyección de dependencias ya que se requiere de otro elemento de software que proporcione una instancia de Logger que pueda utilizar GenericClass.

Con lo anterior podemos crear una definición simple y sencilla.

“La inyección de dependencias es cuando los componentes de un sistema reciben sus dependencias mediante su constructor, métodos o sus propiedades y dichos componentes no obtienen sus dependencias por ellos mismos.”

Definición de inyección de dependencia

La inyección de dependencias reduce el acoplamiento entre clases, de manera que los cambios en una clase no afecten a la otra. Asimismo, las clases pueden ser probadas de forma aislada.

Tipos de inyección de dependencias

De acuerdo a la definición se puede deducir que existen tres tipos de inyección, que podemos apreciar en los listados 1, 2 y 3. En realidad, la inyección vía propiedades no es muy recomendable dado que expone detalles del cliente, pero la incluimos simplemente como ejemplo.

Listado 1. Inyección vía constructor

class GenericClass
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function send($message)
    {
        return $this->logger->info($message);
    }
}

Listado 2. Inyección vía métodos

class GenericClass
{
    pivate $logger;

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function send($message)
    {
        return $this->logger->info($message);
    }
}

Listado 3. Inyección vía propiedades

class GenericClass
{
    public $logger;

     public function send($message)
    {
        return $this->logger->info($message);
    }
}

Veamos como emplear la inyección en Laravel.

Service Container

El Service Container es el encargado de proporcionarnos las facilidades de la inyección de dependencia y principalmente cubre las siguiente tareas:

  • Crear objetos.
  • Determinar las dependencias de un objeto.
  • Crear el objetos con todas sus dependencias.

Es decir que el Service Container se encarga de construir objetos por nosotros.

Esto podemos hacerlo de dos formas:

  • Mediante el uso de bind.
  • Mediante el uso de Singleton.

Instruye al Service Container mediante bind o singleton.

Para que el contenedor pueda construir nuestros objetos como requerimos, es necesario que le indiquemos al contenedor como hacer su trabajo; esto se realiza mediante el uso del método bind y singleton.

También toma en cuenta que estos métodos se acceden desde el AppServiceProvider en el método register.

Usando Bind

Este método recibe el nombre de la clase o interfaz que vamos a registrar y nos devolvera un objeto completamente instanciado con sus dependencias.

$this->app->bind(GenericClass::class, function ($app) {
    return new GenericClass(new Logger);
});

Usando Singleton

Este método realiza exactamente lo mismo que bind: pero este ultimo garantiza que regresara la misma instancia sin importar cuantas veces se solicite la clase en la misma petición.

$this->app->singleton(GenericClass::class, function ($app) {
    return new GenericClass(new Logger);
});

Bind de interfaces

Puedes registrar la implementación de interfaces de la siguiente forma

$this->app->bind(
    LoggerInterface::class,
    Logger::class
);

Inyección de dependencias mediante método.

Los ejemplos que acabamos de ver realizan la inyección mediante el constructor de las clases, si quieres hacer uso de la inyección mediante métodos.

puedes hacer lo siguiente.

$this->app->bind(GenericClass::class, function ($app) {
    return (new GenericClass())->setLogger(new Blogger);
});

Como obtener nuestros objetos del Service Container.

Muy bien ya que el contenedor sabe como va a armar tus objetos, es hora de usarlos.

Esto lo puedes hacer muy facil accediendo al método make del contenedor o usando el helper resolve.

$generic = $this->app->make(GenericClass::class);
// ó
$generic = app()->make(GenericClass::class);
// ó
$generic = resolve(GenericClass::class);

Si por alguna razón, alguna dependencia de la clase que estas solicitando no se puede resolver, puedes usar el método makeWith, que pasa un arreglo con las dependencias que quieres inyectar.

$api = $this->app->makeWith('GenericClass', ['logger' => new Logger]);

Inyección automática

También puedes obtener instancias inyectando la clase que quieres usar en los constructores de los controladores, middleware, etc.

veamos un ejemplo sencillo.

class GenericController extends Controller
{
    /**
     * The generic class instance.
     */
    protected $generic;

    /**
     * Create a new controller instance.
     *
     * @param  GenericClass  $generic
     * @return void
     */
    public function __construct(GenericClass $generic)
    {
        $this->generic = $generic;
    }
}

Para finalizar si tus clases reciben sus dependencias mediante el constructor, no es necesario registrarlas mediante el uso de bind o singleton. Lo único que tienes que hacer es solicitar al contenedor que resuelva la clase y el se encargara de darte la instancia de forma automática!

Así que esto es valido,

$generic = resolve(GenericClass::class);

Si quieres saber mas sobre este tema te aconsejo que leas la documentación oficial, ya que puedes encontrar en ella otros usos interesantes del Service Container de Laravel,

También puedes ver en este articulo como puedes aprovechar la inyección de dependencias para simplificar y mejorar tu código

Por ultimo si este articulo te pareció interesante o tienes algún duda deja tu mensaje en los comentarios.

Y recuerda no olvides compartir este articulo!.