Dice: librería para contenedor de dependencias en PHP

  • Por
Vamos a conocer y practicar con Dice, una librería de PHP sencilla para implementar el patrón contenedor de dependencias en PHP.

Esta entrega pretende dar un poco de luz sobre cómo se podría implementar un patrón de diseño de orientación a objetos como es la inyección de dependencias, muy utilizado, no solo en PHP sino en Java y otros lenguajes orientados a objetos.

Viene a ampliar un poco más la información que ya hemos ofrecido en anteriores entregas, por lo que si no sabes de qué va esto, te recomendamos la lectura de un artículo anterior, donde pudimos aprender qué es la inyección de dependencias.

La librería que vamos a tratar en el presente artículo se llama Dice y la hemos elegido por ser bastante sencilla. Eso nos facilitará centrarnos en el patrón de diseño y ver con más facilidad las ventajas que nos aporta. Sin embargo, a pesar de su sencillez no deja de ser una librería bastante completa, que hace con poco o casi nada de código lo que otras librerías requieren una configuración mucho más detallada.

Esto es así porque Dice tiene la filosofía de anteponer la convención sobre la configuración. Quiere decir que, en base a nuestro código, si estamos usando correctamente las convenciones de orientación a objetos, es capaz de funcionar sin siquiera necesitar realizar configuración alguna, o si es necesaria, será la mínima posible.

Instalación de Dice

Podríamos descargar la librería desde Github en la página del repositorio:

https://github.com/TomBZombie/Dice

Sin embargo, como ya viene siendo habitual en PHP, la configuración más recomendada es vía composer. Puedes crear tu composer.json declarando la siguiente dependencia.

"require": {
	"tombzombie/dice": "dev-master",
}

Nota: Si no sabes lo que es Composer o no sabes exactamente cómo configurar el composer.json, solo tienes que informarte en nuestro Manual de Composer.

Una vez instalado, recuerda hacer el require del autoload, ya en el archivo de PHP donde pretendas usar Dice.

require "vendor/autoload.php";

Clases con dependencias

A continuación vamos a hacer algunos ejemplos de uso de Dice, que nos ilustren cómo funciona y de paso cómo de una manera extremadamente sencilla se aplica este patrón de diseño de inyección de dependencias. Recuerda simplemente que nuestra clase Dice hará las veces de contenedor de dependencias, por lo tanto, la instanciación de los objetos la realizaremos a través de la librería, en vez de hacer los "new" por nosotros mismos desde código PHP.

Para comenzar debemos implementar algunas clases que nos sirvan para jugar con este patrón y la librería Dice. Vamos a suponer una clase "Ordenador" (un computador) que para ser creada necesita dependencias como su "Pantalla" o la "UnidadProcesamiento". La Unidad de Procesamiento (CPU) a su vez depende de la "Memoria" y "Procesador". Insisto que son clases "tontas" únicamente creadas con la intención de que unas clases dependan de otras. Como verás algunas incluso están completamente vacías de datos o funcionalidad.

class Ordenador {
    private $pantalla;
    private $unidad_procesamiento;
    
    public function __construct(Pantalla $p, UnidadProcesamiento $up){
        $this->pantalla = $p;
        $this->unidad_procesamiento = $up;
    }
}

class Pantalla {    
}

class UnidadProcesamiento{
    private $memoria;
    private $procesador;
    
    public function __construct(Memoria $m, Procesador $p){
        $this->memoria = $m;
        $this->procesador = $p;
    }
}

class Memoria{
}

class Procesador{  
}

Antes que entrar a hablar en particular de Dice quiero señalar unos detalles importantes sobre el código anterior. Debes observar que los constructores de algunas de las clases (aquellas que requieren dependencias) tienen en sus parámetros establecidos los tipos de objetos que necesitan para ser instanciados. Sus cabeceras son las siguientes:

public function __construct(Pantalla $p, UnidadProcesamiento $up)
public function __construct(Memoria $m, Procesador $p)

Sobre ese código debes reparar en dos cosas:

  • En los constructores no instanciamos las dependencias que son necesarias para instanciar la clase, sino que las recibimos por parámetro. Esa es la base de la inyección de dependencias, como pudiste leer en el anterior artículo.
  • En PHP, a pesar de ser un lenguaje levemente tipado, podemos especificar en las cabeceras de las funciones las clases de los parámetros de tipo objeto. Es el primer guiño de PHP a los lenguajes fuertemente tipados y es algo habitual cuando estamos implementando polimorfismo. Pero en el caso de Dice es extremadamente necesario, pues esas clases de objetos declaradas en las cabeceras de las funciones son las que ayudarán a la librería para conocer las dependencias que requiere cada objeto.

Ejemplos sencillos del contenedor de dependencias Dice

Con el código anterior ya podemos ponernos a jugar con Dice. En realidad verás que es muy sencillo. Como decíamos, la tarea de crear los objetos la delegaremos a la librería.

$dice = new DiceDice;
$o = $dice->create("Ordenador");

En la primera línea hemos instanciado el contenedor de dependencias. En la segunda línea hemos creado un objeto de la clase Ordenador.

Podrás observar que para crear un ordenador, si no hubiésemos usado Dice, tendríamos que haber creado previamente un Procesador, una Memoria, una UnidadProcesamiento, una Pantalla. Esto hubiera requerido varias líneas de código y Dice lo consigue con solo una. Por supuesto, a más dependencias necesarias, más líneas de código ahorraremos.

Ahora bien, las clases no siempre son tan simples y muchas veces no dependen solo de objetos, sino también de enviarles valores de tipos primitivos. En esos casos, para que el contenedor de dependencias pueda hacer su trabajo necesitamos cambiar un poco la manera de trabajar.

Vamos a cambiar el constructor de la clase Ordenador, haciendo que ahora necesite enviarle un modelo.

public function __construct(Pantalla $p, UnidadProcesamiento $up, $modelo){
    $this->pantalla = $p;
    $this->unidad_procesamiento = $up;
    $this->modelo = $modelo;
}

En este caso tendremos que invocar al contenedor de dependencias enviando el valor del parámetro $modelo. Es bien sencillo y lo podemos definir con el método create().

$o = $dice->create("Ordenador", ["HP Presidiario 4.0"]);

Crearemos también otros modelos de ordenador, indicando otros valores:

$o2 = $dice->create("Ordenador", ["Surface ME"]);

Como puedes observar, indicas en un array cualquier número de parámetros con los valores de tipos primitivos que necesite el método constructor de aquel objeto que deseas crear.

Definición de reglas

Vamos a acabar con una sencilla regla de configuración de Dice, que nos permite explicarle ciertas cosas al contenedor de dependencias. Existen diversos tipos de reglas que se pueden configurar, nosotros vamos a ver una para indicarle parámetros en dependencias. Porque el ejemplo anterior estaba muy bien cuando queremos indicar un valor de un parámetro que es un tipo primitivo, para el objeto que se desea crear, pero ¿Qué pasa cuando el valor del tipo primitivo está en alguna de sus dependencias?

En una clase podemos requerir parámetros de configuración. Por ejemplo, en la memoria, podríamos necesitar indicar su tipo.

class Memoria{
    private $tipo;
    
    public function __construct($t){
        $this->tipo = $t;
    }
}

Nota: Podríamos crear la clase memoria con un constructor que recibe un parámetro con valor predeterminado, así el inyector de dependencias nos creará ese elemento con un valor por defecto sin necesidad de crear ninguna regla de configuración.

class Memoria{
    private $tipo;
    
    public function __construct($t = "DDR3"){
        $this->tipo = $t;
    }
}

Pero si no somos nosotros los que creamos esa clase (y los creadores no le pusieron ese valor predeterminado al declarar la función) quizás no podemos (Generalmente tampoco queremos) tocar el código de la clase. En ese caso se lo tenemos que pasar de alguna manera. Sin embargo como ese valor es un tipo primitivo (una cadena en este caso) no te lo va a generar el inyector de dependencias si no lo configuras.

Afortunadamente, la creación de reglas es muy sencilla en Dice. La puedes ver resumida en el siguiente código.

// instancio la regla para configurar Dice
// Luego usaré la regla en el constructor de la clase Memoria
$rule = new DiceRule;

// asigno un comportamiento a esta regla
$rule->constructParams = ['DDR2'];

// asocio esta regla a la clase Memoria
$dice->addRule('Memoria', $rule);

Una vez creada esa regla, podemos generar ordenadores y en los tipos de memoria se le colocará el valor 'DDR2' definido. El uso del método create será exactamente el mismo, siendo indiferente que internamente se use esa regla.

$o2 = $dice->create("Ordenador", ["MacBook Pro"]);

Existen otros tipos de reglas, necesarios para usos más avanzados y concretos. Los podremos ver más adelante en futuros artículos si es vuestro interés.

En futuros artículos experimentaremos con alguna librería para implementar de manera sencilla un contenedor con el que poner en marcha este patrón de inyección de dependencias.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir

Comentarios

Domingo Oropeza

27/5/2015
Otra libreria de inyeccion
Existe tambien otro bundle de contenedores de dependencias hecho por el equipo de Symfony
https://github.com/symfony/DependencyInjection
Sirve sin necesidad del framework completo.