¿Cómo decorar Servicios?

symfony_1_1.jpg
Solucionex
26
Dic 19

Cuando tenemos la necesidad en symfony de añadir una nueva funcionalidad a un servicio ya existente, una de las mejores opciones es aplicar el patrón de programación decorador.

Aplicarlo es bastante sencillo, entenderlo es, quizá, la parte más complicada.

La documentación oficial de symfony pone el ejemplo de un servicio de correo que necesita ser extendido.

Ejemplo de uso de un decorador en Symphony

En este post vamos a utilizar como ejemplo la ampliación de la funcionalidad de un servicio que obtiene una noticia de la base de datos. Con la nueva funcionalidad queremos saber cuántas veces ha sido leída, así que para ello vamos "decorar" el servicio.

Suponiendo que tenemos un servicio App\Service\ObtainPost.

//src/Service/ObtainPostService.php namespace App\Service; use Doctrine\ORM\EntityManagerInterface; class ObtainPostService { private $em; private $repository; public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->repository = $this->em->getRepository('App:Post'); } public function execute($id) { return $this->repository->find($id); }

Ampliar la funcionalidad de un servicio con un decorador

Para no tocar este código que funciona perfectamente y que se está utilizando en la aplicación con el fin de obtener un post, sin contabilizar que se ha leído, vamos a crearnos un nuevo servicio que decore a este otro.

La única "complicación" que tiene este patrón de desarrollo es que vamos a inyectar el antiguo en el nuevo.

//src/Service/ObtainPostCountingService.php namespace App\Service; use Doctrine\ORM\EntityManagerInterface; class ObtainPostCountingService { private $em; private $obtainPostService; public function __construct(ObtainPostService $obtainPostService, EntityManagerInterface $em) { $this->em = $em; $this->obtainPostService = $obtainPostService; } public function execute($id) { $post = $this->obtainPostService->execute($id); $post->setReadings($post->getReadings() + 1); $this->em->flush(); return $post; }

Sencillo, ¿verdad? Ahora, cada vez que queramos obtener la noticia y contar que ha sido vista una vez más, la obtendremos a través de nuestro nuevo servicio, que decora al anterior que ya existía.

Sobreescribir el servicio antiguo con la nueva funcionalidad

Si aplicamos este patrón en nuestra aplicación web desarrollada con Symfony y decidimos hacerlo a través de la la hoja de servicios (config/services.yaml), Symfony sobreescribe el servicio antiguo, de tal manera, que en todas las partes donde se hubiese inyectado el servicio antiguo, pasará a utilizar el servicio decorado.

Esto es lo que ocurre en el ejemplo de la documentación oficial, y es que tenemos el siguiente servicio:

# config/services.yaml services: App\Services\ObtainPostService: ~

A este servicio le vamos a ampliar su funcionalidad, que es lo que hemos hecho antes con código PHP. Utilizando la configuración con yaml puesta a continuación, todos los sitios donde estuviese inyectado el servicio, pasará a tener la nueva funcionalidad.

# config/services.yaml services: App\Service\ObtainPostService: ~ App\Services\ObtainPostCountingService: decorates: App\Service\ObtainPostService # pass the old service as an argument arguments: ['@App\Service\ObtainPostCountingService.inner'

El servicio ObtainPostCountingService.inner es el servicio sin decorar.

Aunque básicamente es esto, hemos reducido al máximo la explicación para que se pueda entender, se recomienda leer la documentación de Symfony https://symfony.com/doc/4.4/service_container/service_decoration.html, donde explica con más detalles esto mismo, con las posibilidades de customizarlo (por si quisiéramos utilizar alias para los servicios o utilizar otra nomenclatura que no sea esta última ...inner, entre otras cosas).

El pattern decorator

La decoración es un patrón de programación y no algo exclusivo de Symfony, para saber más, también es recomendable leer un poco fuera del entorno de este framework. Este vídeo nos ayuda a entenderlo un poco mejor, ayudándose de explicaciones gráficas en una pizarra.