Inyección de Dependencias en PHP

La milenaria técnica del Service Container

Escrito por Matías Navarro Carter hace 4 semanas
En: PHP / Patrones de Diseño
Lectura de 7 minuto(s).

Si has realizado aplicaciones utilizando PHP por tí mismo sin la ayuda de un framework por el tiempo suficiente seguramente te has topado con el siguiente problema: ¿Cómo puedo compartir mi objeto de conexión de PDO con, por ejemplo, mis Modelos u otras clases?

Sinceramente espero que no hagas algo como esto:

<?php

class User {

    private $pdo;

    public function __construct()
    {
        $this->pdo = new PDO(//...);
    } 

    //...       

}

class Group {

    private $pdo;

    public function __construct()
    {
        $this->pdo = new PDO(//...);
    }

}

¿Por qué el código anterior está mal? Bueno, antes de responder esta pregunta, es necesario repasar un poco sobre principios de programación orientada a objetos.

Clases, Clases, Clases!

Clases son el fundamento de la programación orientada a objetos. Una clase es una especie de cianotipo (plantilla) para un objeto. Seguramente ya sabes que las clases se componen de dos elementos principales: datos y comportamiento. Al menos en PHP, a los datos de una clase de le llaman propiedades, y a las diferentes acciones de comportamiento se les llama métodos.

<?php

class Dog {

    // Éstas son propiedades de Dog, ayudan a definir muchos "dogs" diferentes.
    private $color;

    private $size;

    private $age;

    private $noise;

    // ...

    // Esto es el método bark (ladrar). Define un comportamiento o acción para Dog
    public function bark()
    {
        echo $this->noise;
    }

}

Si todo esto es muy confuso, piensa en una clase como en un array con poderes muy especiales. A mí me ayudó.

Dos tipos de clases

En la práctica, sabemos que clases pueden tener tanto datos como comportamiento. Sin embargo, cuando estás desarrollando aplicaciones web, te encontrarás con clases que tendrán un mayor enfoque en comportamiento, mientras que otras en datos. Esto es especialmente cierto de, por ejemplo, tus modelos y tu conexión de base de datos. Déjame explicar:

Tus modelos son representaciones de datos de una entidad abstracta. En un blog, por ejemplo, tendrás el modelo Article, otro User, otro Category y otro Tag. Estas clases tienen como principal función el representar datos que, por ejemplo, pueden provenir de una base de datos. No se enfocan tanto en realizar otras cosas en tu aplicación. Están allí para ser consumidos por otras clases.

Sin embargo, tomemos el ejemplo anterior de PDO. La función de PDO no es representar datos; la función de PDO es la de proveer una conexión a una base de datos para tu aplicación. Está mucho más enfocada entonces en comportamiento, y no tanto en datos. En otras palabras, esta clase presta un servicio a tu aplicación. Por ello, en desarrollo de software, estos objetos suelen llamarse Servicios, y generalmente una aplicación sólo tendrá una instancia (una copia) de ellos.

Arquitectura Orientada a Servicios

Uno de los paradigmas de diseño de Software más probados y utilizados es el de orquestrar tu aplicación en base a servicios. Piezas importantes de software funcionan así: sistemas operativos, kernels y aplicaciones web.

La idea es que tu aplicación va a funcionar en base a servicios que, mediante las acciones que realizan, irán modificando el "estado" de tu aplicación y ofreciendo el resultado esperado para el usuario final.

Los servicios, por definición, son Singletons (Single Instances, o Instancias Únicas). Esto quiere decir que se instancian una vez en tu aplicación, y listo. Es por eso que el ejemplo del principio es una terrible idea. No podemos tener dos instancias de PDO en nuestra aplicación (a menos que se conecten a bases de datos diferentes). Es una pésima idea, gasta memoria, y gasta el máximo de número de conexiones que SQL puede soportar.

Lo que debemos hacer es utilizar una técnica llamada Inyección de Dependencias.

Inyectando Dependencias

Inyección de Dependencias es en realidad un término fancy para decir que, en vez de que un objeto cree el objeto que necesita, se lo "pasamos", o se lo inyectamos a través del constructor. Algo como esto.

<?php

    $pdo = new PDO(//connection...);

    class User {

        private $pdo;

        public function __construct(PDO $pdo)
        {
            $this->pdo;
        }
    }

    $user = new User($pdo);
    $group = new Group($pdo);

Lo maravilloso de este cambio es que ahora tenemos un sólo objeto PDO que podemos pasar a todas las partes de nuestra aplicación que lo necesiten. Ni User ni Group pueden ni deben crear el objeto de conexión: nosotros se lo tenemos que hacer llegar a ellos.

La inyección de dependencias ayuda a que nuestro código sea más mantenible en el tiempo. ¿Que pasaría, por ejemplo, si siguieramos el ejemplo al principio de este artículo y nuestra conexión a la base de datos cambiara? Tendríamos que encontrar todas las instancias de new PDO que hicimos y modificarlas. ¡Que horrible! Utilizando Inyección de Dependencias, centralizamos los datos de nuestra conexión en un solo lugar, lo que nos permite reutilización y refactorización sin problemas.

Un Ejército de Servicios

Inyección de Dependencias es lo más cool, pero cuando tu aplicación tiene muchos servicios, inyectar dependencias puede volverse una tarea horrible.

    <?php

$servicio1 = new Servicio1('argumento');
$servicio2 = new Servicio2('argumento');
//....
$servicio1000 = new Servicio1000('argumento');

Con muchos servicios, siempre vamos a tener que ir pasando una cantidad gigante de dependencias de un lugar a otro para hacer nuestros servicios disponibles de una parte de la aplicación a otra.

Por suerte, los genios de la informática de antaño también pensaron en esto, e idearon una técnica que se utiliza, de formas variadas, hasta el día de hoy y que es muy popular en el desarrollo web de hoy en día: (redoble de tambores) el Container de Inyección de Dependencias.

Un Container es un objeto que, como su nombre lo dice, contiene TODOS los servicios de tu apliación. Su sola función es pasar los servicios a las partes de tu aplicación que los requieran. Esto resuelve muchos problemas, como por ejemplo, ya no tienes que pasar todos esos servicios a cierta clase. Ahora, puedes pasar, por ejemplo, el Container a tu controlador y de tus controladores puedes llamar a los servicios que necesites.

DI Container 101

Hay muchas implementaciones del patrón Container, pero quizás la más básica es esta:

<?php

class Container {

    private $pdo;

    private $mailer;

    public function getPDO()
    {
        if ($this->pdo === null) {
            $this->pdo = new PDO(//Connection...)        
        }
        return $this->pdo;
    }

    public function getMailer()
    {
        if ($this->mailer === null) {
            $this->mailer = new Mailer(//Settings...)        
        }
        return $this->mailer;        
    }
}

Luego, en tu aplicación:

<?php

$container = new Container();

$pdo = $container->getPdo();

$user = new User($pdo);

Lo que hemos hecho es increíblemente poderoso. Tenemos todos los servicios de nuestra aplicación en un objeto. Y lo que es mejor, el container no los carga hasta que no llamamos al método para obtenerlos, y el if statement dentro hace que se carguen una sola vez.

Este es el poder del Container. Y por si no lo sabías, todos los frameworks modernos de PHP utilizan uno. Las técnicas en cómo obetener y registrar servicios varían de un framework a otro, pero en esencia es el mismo patrón: un objeto que contiene un registro de todos los servicios de tu aplicación.

Ahora, toma esas aplicaciones viejas que escribiste sin la ayuda de un framework, y ponles un container.