Inyectar dependencias
El patrón de diseño IoC: Inversion Of Control
El patrón de diseño (o Design Pattern) Inversion Of Control es un modelo de arquitectura basado en el principio de que el flujo de ejecución de las diferentes instrucciones no lo controla la aplicación en sí, sino un elemento de software proporcionado por un framework. Como parte de Symfony, el Service Container es el que posee esta funcionalidad.
1. Contribuciones en una arquitectura aplicativa
En una aplicación tradicional, es decir, sin framework, el programador relaciona los diferentes componentes aplicativos entre sí. Es él quien se debe encargar en su código de la comunicación entre los elementos del modelo, de la vista y del controlador. Esto implica proporcionar una cantidad importante de «código técnico», además del código funcional, lo que perjudica la productividad de los desarrolladores.
Todos los frameworks de desarrollo actuales tienen más o menos un contenedor de software, que se encargará de la inversión del control. Debemos ver la inversión del control como un mecanismo donde todo el flujo de ejecución ya está trazado (aunque sea configurable) y los componentes aplicativos se conectarán en las ubicaciones adecuadas.
Por lo tanto, la principal ventaja que aporta un contenedor es el control del flujo de ejecución. Esto permite al desarrollador...
Inyectar dependencias
1. Aspectos básicos
La idea que se esconde detrás de la inyección de dependencias es simple: en lugar de dejar que una clase obtenga las dependencias que necesita, este proceso se externaliza.
La clase X, aligerada de esta tarea, se podrá concentrar en su función principal.
A continuación, vamos a descubrir los diferentes tipos de inyección.
2. Las diferentes técnicas de inyección de dependencias
a. Inyectar dependencias por parte del constructor
Esta inyección consiste en pasar la dependencia durante la instanciación de la clase:
<?php
class X
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function foo()
{
$stmt = $this->db->query('SELECT...');
// ...
}
// ...
}
Este tipo de inyección es ideal para dependencias...
El Service Container
Hemos mencionado esta entidad en el capítulo sobre la arquitectura del framework. Sabemos que el Service Container contiene «servicios».
Su función es absorber la complejidad que acabamos de identificar: definir la forma de instanciar clases pasivas, conteniendo solo reglas y posibilidades de inyección. A esta definición se la denomina servicio (service).
Concretamente, el Service Container es una clase en la que cada método devuelve un servicio.
1. Servicios
Un servicio es un objeto PHP que está listo para usar y tiene una tarea genérica y única. Se puede utilizar para hacer que «persistan» datos en una base de datos, enviar correos electrónicos o escribir registros de actividad o logs.
Un servicio es accesible a través del Service Container y el framework Symfony tiene por defecto una serie de servicios.
2. Explicaciones a través de un servicio X
Ahora vamos a ver la creación de un servicio X dado, basado en nuestra clase X (ver la definición de la clase en el inicio de este capítulo).
Todos los servicios se definen en el Service Container y son accesibles solo a través de él; la creación de este servicio se realizará en la clase que representa el Service Container:
<?php
class ServiceContainer
{
private $services...
Crear un servicio y configurar sus inyecciones
1. Crear un servicio
La forma más fácil de crear un servicio es configurarlo a través del archivo config/services.yaml. Los servicios se deben definir bajo la clave services:
# config/services.yaml
services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
Esta configuración crea un servicio my_pdo, que es una instancia de PDO (clave class). Los argumentos que se pasan al constructor se definen bajo la clave arguments. Concretamente, se crea el servicio my_pdo con esta configuración de la siguiente manera:
<?php
$service = new PDO('mysql:host=localhost', 'bilal', 'pass');
2. Los diferentes tipos de inyecciones en un servicio Symfony
a. Inyección por constructor
Una vez que el servicio my_pdo está configurado, se puede inyectar en otro servicio a través del constructor, por ejemplo (ver Inyectar dependencias por parte del constructor, anteriormente en este capítulo):
# config/services.yaml
services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
arguments: @my_pdo
El servicio X se crea así:
<?php
$myPDO = $serviceContainer->get('my_pdo');
$service = new X($myPDO);
Aquí, el Service...
Carga automática de servicios
La carga automática de servicios es una funcionalidad que apareció en Symfony con la versión 3.4 y ahora su uso está muy extendido en la implementación de aplicaciones Symfony.
1. Configuración
El principio es bastante sencillo: todas las clases que cree (con algunas excepciones) se guardan como un servicio y, por lo tanto, se inyectan entre sí. También puede inyectar servicios Symfony en ellos. Este comportamiento es posible gracias a la configuración predeterminada proporcionada en el archivo config/services.yaml, cuyo contenido se muestra a continuación, con la configuración en negrita:
services:
# default configuration for services in *this* file
defaults:
autowire: true # Automatically injects dependencies in
# your services.
autoconfigure: true # Automatically registers your services as
# commands, event subscribers, etc. ...
Crear servicios reutilizables y distribuibles
1. El concepto de bundle
En versiones anteriores del framework (antes de Symfony 4 para ser exactos), todo el código de una aplicación se tenía que organizar en bundles. El objetivo de este enfoque era facilitar la reutilización y distribución de funcionalidades. Aunque este sistema de organización ya no es obligatorio, todavía se puede utilizar si desea desarrollar funcionalidades y redistribuirlas.
a. Crear un bundle
Para crear servicios redistribuibles, primero es necesario crear un bundle y la estructura de carpetas y archivos que lo acompañan. La notación de los bundles sigue convenciones bastante precisas. Por ejemplo, si desea desarrollar un conjunto de funcionalidades de administración, asigne al bundle el nombre MiEmpresaAdminBundle siguiendo estas convenciones. A continuación, se muestra la estructura básica del bundle, que consiste en una clase en la arborescencia de carpetas indicada en el comentario, en la parte superior del código:
// src/MiEmpresa/AdminBundle/MiEmpresaAdminBundle.php
namespace App\MiEmpresa\AdminBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MiEmpresaAdminBundle extends Bundle
{
}
Para activar este bundle, es necesario declararlo en el archivo config/bundles.php con la siguiente sintaxis:
return [
// ...
App\MiEmpresa\AdminBundle\MiEmpresaAdminBundle::class
=> ['all' => true],
];
b. Arborescencia de bundles
Una vez creado el bundle, es necesario añadir directorios para organizar el código. He aquí la estructura típica recomendada (aunque algunos directorios pueden ser innecesarios dependiendo de las funcionalidades que desarrolle).
-
Controller/: contiene los controladores del bundle.
-
DependencyInjection/: contiene clases de inyección de dependencia; volveremos sobre esto en el resto de esta sección.
-
Resources/config/: contiene la configuración del bundle; por ejemplo, un archivo routing.yaml si el enrutamiento de este bundle se declara en formato YAML.
-
Resources/views/: contiene las plantillas de Twig.
-
Tests/: contiene las clases de prueba del bundle.