Symfony: tutorial 10: los servicios, la inyección de dependencias

Los servicios, la inyección de dependencias

Continuando con la actualización a Symfony 4, repasando de parte a parte todas las mejoras que nos trae ésta nueva versión, llegamos al contenedor de servicios. Ésto es la madre del cordero de Symfony, tenemos un contenedor de servicios, muy rápido, en donde vamos a organizar nuestra aplicación web lo mejor posible.

Los servicios simplemente son clases de PHP que podrás utilizar mediante el contenedor de servicios en todas las partes de tu aplicación Symfony. Si no estás usando esta inyección de dependencias, y creando tus propios servicios para organizarlo todo.. con este post lo vas a ver muy sencillo.

Éste post va de Código Limpio y de Ingeniería del Software. Para el Código Limpio tenemos una serie de técnicas de programación, como la buena ortografía o el bien redactar pero para programar. Y para la Ingeniería del Software tenemos que hacer que la aplicación esté bien estructurada, que esté bien dividida en bloques o capas para que se pueda mantener y que muchas personas puedan participar en el proyecto sin dar un grito al cielo.

Así que tenemos que conseguir que funcione todo bien, que sea mantenible y que la aplicación pueda crecer en buenas condiciones..

Mi caso, qué me dice la experiencia

En mi caso, tiendo a crear controladores y comandos muy grandes, con muchas líneas de código. La idea es que la mayor parte del código fuente debería de acabar, bien organizado en un montón de servicios. En estos servicios es donde organizaremos todo en módulos, capas o como quieras llamarlos.

Suelo construir a todo trapo a base de controladores bastante grandes. Si tengo claro desde el inicio cómo estructurarlo todo, comienzo por crear los servicios que van a formar las partes. Pero no es lo habitual, ya que siempre es urgente o hay poco tiempo..

Por ejemplo, todo lo relacionado con los emails debería de estar dentro de los servicios, en ficheros que tengan en el nombre la palabra email. O los ficheros relacionados con crear ficheros CSV que tengan en el nombre la palabra CSV. Parece obvio, pero si no se tiene en mente esto, es fácil acabar duplicando códigos, no encontrando donde están las cosas. O lo que es peor, si no trabajamos así será imposible trabajar en equipo.

Si más personas trabajan en el proyecto y no está todo bien estructurado, no nos aclararemos para dividir el trabajo, nos machacaremos, duplicaremos códigos, será imposible trabajar sin borrar y reconstruir constantemente, será un caos de código inmantenible, mal probado y sin estructura. Incluso nosotros mismos no recordaremos donde está todo al cabo de unos meses.

Debemos de encapsular todo lo que podamos en clases de PHP que serán los servicios.

Resumiendo xDDD

Qué es la inyección de dependencias, teoría

La inyección de dependencias es un componente de Symfony que implementa el estándar PSR-11 a fecha en que escribo para que puedas cargar tus servicios por todas partes de tu aplicación.

Parafraseando la documentación oficial. Crearemos en el directorio srv/Service/ un monton de clases de PHP con la mayor parte del código fuente. Y mediante el contenedor de servicios los iremos instanciando allí donde los necesitemos.

En la primera vez que usemos un servicio, éste será creado. Y si se vuelve a necesitar el servicio de nuevo en otra parte del código éste servicio permanece ya creado en el contenedor de servicios, con lo que no se gasta tiempo en volver a crearlo en memoria. Si un servicio no se usa en una petición a la web, simplemente no se crea, no se gastará memoria ni tiempo en crearlo, con lo que en este caso la petición se ejecutará más rápido.

Listando, viendo los servicios disponibles

Llendo al contenido práctico de este post, podemos listar todos los servicios así:

php bin/console debug:container

Verás un listado de códigos de servicios y las clases que hay que usar para usarlos. Un proyecto Symfony estándar nos provee de muchos servicios, conocerlos y saber cómo usarlos será el día a día del trabajo en Symfony.

Creando un servicio propio

El siguiente paso es crear un servicio propio. Podría ser por ejemplo un servicio para manejar variables de configuración. Una primera versión de este servicio podría ser un fichero llamado src/Service/CoreConfigManager.php que quedara así:

<?php

namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;

class CoreConfigManager
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function getValue($code)
    {
        $coreConfigItem = $this->entityManager
        ->getRepository('App:CoreConfig')
        ->findOneByCode($code);
        if ($coreConfigItem) {
            return $coreConfigItem->getValue();
        } else {
            return '';
        }
    }
}

Lo que hace este servicio ahora mismo es simplemente servir el valor de una variable de configuración con cierto código, con la función getValue que marco en negrita.

Fíjate que a su vez el manejador de entidades lo he inyectado en el constructor. Ésta misma estrategia la vamos a usar para usar los servicios en los controladores a continuación.

Inyectando un servicio dentro de un controlador

Lo siguiente sería ver cómo usar este servicio anterior en cualquier controlador. Esto lo consigues añadiendo código como el siguiente:

use App\Service\CoreConfigManager;

class UnNombreCualquieraController extends Controller
{
    /**
    * @Route("/una-url", name="una_url")
    */
    public function unaAccionCualquiera(CoreConfigManager $coreConfigManager)
    {
        $someValue = $coreConfigManager->getValue('codigoDeVariable');

Éste podría ser el inicio de un controlador. Fíjate que con respecto a las versiones 2 y 3 de Symfony que se ha simplificado enormemente el uso de servicios. Ya no hay que declararlos en ficheros Yaml, ni tampoco hay que cargarlos desde el contenedor de servicios.

Inyectando un servicio dentro de otro servicio

Ésto es lo que hemos visto en el servicio de ejemplo, el servicio de manejo de entidades de Doctrine viene inyectado dentro del servicio de manejo de configuraciones que estamos creando. Si nos fijamos de nuevo, tenemos que en el constructor de la clase se recibe un parámetro, que a su vez se guarda como variable interna. En el constructor de la clase de nuestro nuevo servicio se le inyecta el manejador de entidades de Doctrine al llamarlo así:

public function __construct(EntityManagerInterface $entityManager)

Éste constructor recibe el servicio de manejo de entidades de Doctrine porque se ha usado la clase EntityManagerInterface. Se guarda en una variable interna, y así simplemente ya podemos trabajar con los datos de la base de datos dentro de este servicio al acceder a la variable de la clase:

$this->entityManager

Inyectando un servicio en todos los Twigs de un proyecto

Para terminar con éste post, éste es otro uso de los servicios que me ha resultado muy útil en varios proyectos. Es una forma de inyectar en todas las plantillas de Twig un servicio en concreto. Así no tendremos que cargar este servicio en el controlador, para pasarlo a la plantilla como parámetro, sino que directamente en las plantillas ya lo tendremos disponible.

Esto se hace simplemente añadiéndo en el fichero config/packages/twig.yaml las siguientes configuraciones:

twig:
    globals:
        coreConfigManager: '@App\Service\CoreConfigManager'

..así desde dentro de fichero .twig podremos hacer cosas como lo siguiente:

{% if coreConfigManager.getValue('unCódigoDeVariable') == 'true' %}
    {# Hacemos algo.. #}
{% endif %}

Jugando con esto, podríamos tener por ejemplo bloques con HTML/CSS/Javascript que guardamos en la base de datos, y los vamos inyectando en todos lados en los ficheros Twig. Por ejemplo, si definiéramos un manejador de bloques con una entidad que te guarde bloques en la BD, podríamos gestionarlos en el backend, y en el frontend inyectarlos con una sola línea en el fichero Twig plantilla. Quedaría algo así:

{{ blockManager.getContent('unCódigoDeBloque') | raw }}

Dándole unas vueltas a ésto, podríamos por ejemplo tener una plantilla Twig que cargara la estructura de una web así:

<html>
<head>
    <title>{{ coreConfigManager.getValue('titlePage1') }}</title>
</head>
<body>
    <h1>{{ coreConfigManager.getValue('titlePage1') }}</h1>
    {{ blockManager.getContent('menu') | raw }}
    <p>Aquí el contenido principal de esta página..</p>
    <p>Aquí el contenido principal de esta página..</p>
    <p>Aquí el contenido principal de esta página..</p>
    {{ blockManager.getContent('footer') | raw }}
</body>
</html>

Terminando

Recapitulando, que casi todo el código fuente de tu aplicación web debe de acabar en ficheros de Servicios. Todo debe acabar bien organizado y bien estructurado en forma de Servicios. Éstos Servicios son los que a su vez podremos tener bien testeados, con sus pruebas unitarias, todo bien refactorizado.

Los controladores tienen que ser simples llamadas de no más de 20 líneas, quizá 4-5 líneas que hagan lo imprescindible para llamar al servicio que toque. Los controladores más grandes sólo deberían de ser los que manejan formularios o acciones muy simples para las que quizá no merece la pena encapsularlas en un servicio.

Si tienes una aplicación web Symfony sin servicios, o con muy pocos.. ¡Ojo! O está en pañales, o es muy pequeña, o.. tienes que refactorizar organizando todo sino tu aplicación tiene mucho peligro de convertirse en algo inmanejable. O lo que es aún peor, cuando alguien entre a colaborar en el proyecto, no va a poder participar, y va a clamar al cielo pidiendo una refactorización a fondo de todo lo que hayas hecho 😉

Ya con esto cierro post, otro día más.. ¡Un saludo!

Compartir..

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

4 × dos =

2 ideas sobre “Symfony: tutorial 10: los servicios, la inyección de dependencias”