miércoles, 25 de junio de 2025

Implementando el Patrón Specification en PHP con Doctrine Criteria

Implementando el Patrón Specification en PHP con Doctrine Criteria

El Patrón Specification es un patrón de diseño que encapsula la lógica de negocio necesaria para validar si un objeto satisface un criterio particular. Combinado con el poder de Doctrine Criteria, podemos crear consultas dinámicas y altamente mantenibles para nuestras entidades. Este enfoque separa la lógica de selección de datos del acceso a datos, mejorando la flexibilidad y la reutilización del código.


<?php

use Doctrine\ORM\Query\Expr\Comparison;
use Doctrine\ORM\QueryBuilder;

interface SpecificationInterface
{
    public function modifyQuery(QueryBuilder $qb, string $alias): void;
}

class AndSpecification implements SpecificationInterface
{
    private SpecificationInterface $left;
    private SpecificationInterface $right;

    public function __construct(SpecificationInterface $left, SpecificationInterface $right)
    {
        $this->left = $left;
        $this->right = $right;
    }

    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $this->left->modifyQuery($qb, $alias);
        $this->right->modifyQuery($qb, $alias);
    }
}

class IsActiveSpecification implements SpecificationInterface
{
    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $qb->andWhere($alias . '.isActive = :isActive')
           ->setParameter('isActive', true);
    }
}

class ProductNameContainsSpecification implements SpecificationInterface
{
    private string $searchTerm;

    public function __construct(string $searchTerm)
    {
        $this->searchTerm = $searchTerm;
    }

    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $qb->andWhere($qb->expr()->like($alias . '.name', ':searchTerm'))
           ->setParameter('searchTerm', '%' . $this->searchTerm . '%');
    }
}
    

En el ejemplo anterior, definimos una interfaz SpecificationInterface con un método modifyQuery que permite modificar un QueryBuilder de Doctrine. Implementamos especificaciones concretas como IsActiveSpecification para filtrar entidades activas y ProductNameContainsSpecification para buscar productos por nombre. También incluimos una especificación AndSpecification para combinar especificaciones. La clave está en usar el QueryBuilder de Doctrine para construir las consultas dinámicamente.


<?php

use Doctrine\ORM\EntityManagerInterface;

class ProductRepository
{
    private EntityManagerInterface $entityManager;

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

    public function findBySpecification(SpecificationInterface $specification): array
    {
        $qb = $this->entityManager->createQueryBuilder();
        $qb->select('p')
           ->from('App\Entity\Product', 'p');

        $specification->modifyQuery($qb, 'p');

        return $qb->getQuery()->getResult();
    }
}

// Uso:
// $isActiveSpec = new IsActiveSpecification();
// $nameContainsSpec = new ProductNameContainsSpecification('Producto');
// $combinedSpec = new AndSpecification($isActiveSpec, $nameContainsSpec);

// $products = $productRepository->findBySpecification($combinedSpec);

    

Finalmente, en el repositorio, el método findBySpecification recibe una instancia de SpecificationInterface, crea un QueryBuilder, aplica la especificación y ejecuta la consulta. El código de uso muestra cómo combinar especificaciones para crear consultas complejas de forma concisa y mantenible. Este enfoque promueve la separación de preocupaciones, facilitando las pruebas unitarias y la evolución del sistema.

No hay comentarios:

Publicar un comentario