Cómo decorar nuestros repositorios en Symfony

El patrón “decorador” aplicado a los repositorios de Doctrine de Symfony

Image for post
Image for post

Favorece la composición frente a la herencia

Además, ésto nos va a permitir repasar el patrón decorador, el cual, si no lo conocéis, permite añadir funcionalidad en tiempo real a nuestros objetos decorándolos con nuevos métodos que son añadidos mediante composición. De este modo, no ahorramos tener que crear una superclase con los métodos que queremos añadir y que después nuestras clases extiendan de dicha clase para adquirirlos.

Descripción del problema

Imaginad dos entidades A y B (las cuales no tienen que ver la una con la otra en nada) que sin embargo poseen todas la misma propiedad createdAt y el cliente quiere que le hagamos el típico panel de estadísticas con las entidades que se han ido creando para cada tipo según el día seleccionado.

class RepositoryA {  public function findTodayStats(): int {    $today = new \DateTime();    return $this->createQueryBuilder('o')      ->select('COUNT(o) as total')      ->andWhere('o.createdAt = :today')      ->setParameter('today', $today)      ->getQuery()      ->getSingleScalarResult();    ;  }}

Solución con herencia

Es aquí donde se nos puede iluminar la bombilla y decir… Oye. Creamos una clase padre y que todos los repositorios extiendan de ella para obtener el mismo método:

class ParentRepository {  public function findTodayStats(): int {    $today = new \DateTime();    return $this->createQueryBuilder('o')      ->select('COUNT(o) as total')      ->andWhere('o.createdAt = :today')      ->setParameter('today', $today)      ->getQuery()      ->getSingleScalarResult();    ;  }}class RepositoryA extends ParentRepository { // }class RepositoryB extends ParentRepository { // }

El patrón decorador

Pese a que no sea una de las características más conocidas de Symfony, es posible decorar nuestros servicios para añadirles nueva funcionalidad sin tener que recurrir a la herencia. Y, salvo que hayamos especificado que las clases de nuestra carpeta Repository no sean inyectadas como repositorios, todos ellos serán ya de por sí servicios que podemos inyectar directamente (aunque la práctica más habitual sea inyectar el EntityManagerInterface y recoger de ahí los repositorios).

Vale muy bien, pero… ¿y ahora cómo empleamos este repositorio decorado?

Supongamos que necesitamos emplear el repositorio de la clase A pero con el método findTodayStats decorado en un servicio cualquiera ( AnyService ).

use App\Repository\RepositoryA;class AnyService {  public function __construct(RepositoryA $repo) {

$this->repo = $repo
} .... // rest
app.repository.a.stats:  class: App\Repository\EntityStatsRepositoryDecorator  decorates: App\Repository\RepositoryA  // no necesario a partir de Symfony 4.2  arguments:    $repository: '@app.repository.a.stats.inner'
  • La línea 2 declara la clase que usa, que en nuestro caso es la clase que hemos creado anteriormente App\Repository\EntityStatsRepositoryDecorator .
  • La línea 4 declara los argumentos de nuestro servicio, y puesto que RegistryInterface ya se inyecta mediante el autowire de Symfony, lo que haremos será declarar que la propiedad $repository de nuestro constructor será el servicio que está siendo decorado por este servicio: @app.repository.a.stats.inner es decir, App\Repository\RepositoryA .
App\Service\AnyService

arguments:
$repo: '@app.repository.a.stats'
services:  _defaults:    autowire: true    autoconfigure: true    bind:      $aRepoWithStats: '@app.repository.a.stats'
use App\Repository\RepositoryA;class AnyService {public function __construct(ServiceEntityRepository $aRepoWithStats) {

$this->repo = $aRepoWithStats
}.... // rest
app.repository.b.stats:  class: App\Repository\EntityStatsRepositoryDecorator  decorates: App\Repository\RepositoryB  arguments:    $repository: '@app.repository.b.stats.inner'
bind:  $bRepoWithStats: '@app.repository.b.stats'

¿Quieres recibir más artículos como este?

Suscríbete a nuestra newsletter:

Entre paseo y paseo con Simba desarrollo en Symfony y React

Get the Medium app