Blog


Filtrando colecciones con Doctrine en un proyecto Symfony2

FILTRANDO COLECCIONES CON DOCTRINE EN UN PROYECTO SYMFONY2

26 / 11 / 2013 Symfony

Desarrollando tu aplicación web con Symfony2 y Doctrine es probable que te encuentres con la necesidad de tener que mostrar solo algunos elementos relacionados con una determinada entidad. En este artículo, vamos a ver cómo podemos solucionar este problema, mediante el filtrado de colecciones de Doctrine.

En el ejemplo con el que vamos a trabajar, tendremos una entidad Producto, que mantiene una relación 1:N con Categoría y a su vez una relación 1:N con ProductoTipo. A groso modo, estas serían las entidades definidas en nuestro proyecto Symfony, con las anotaciones correspondientes:


class Categoria
{
    ...

    /**
     * @ORMOneToMany(targetEntity="Producto", mappedBy="categoria", cascade={"persist"})
     */
    protected $productos;

    ...
 
    public function __construct()
    {
        $this->productos = new ArrayCollection();
    }

    ...
 
    /**
     * Get Productos
     *
     * @return Collection
     */
    public function getProductos()
    {
        return $this->productos;
    }

    ...
}


class Producto
{
    ...

    /** 
     * @ORMManyToOne(targetEntity="Categoria", inversedBy="productos")
     * @ORMJoinColumn(name="categoria_id", referencedColumnName="id")
     */
    protected $categoria;

    /** 
     * @ORMManyToOne(targetEntity="ProductoTipo", inversedBy="productos")
     * @ORMJoinColumn(name="producto_tipo_id", referencedColumnName="id")
     */
    protected $producto_tipo;
}


class ProductoTipo
{
    ...

    /**
     * @ORMOneToMany(targetEntity="Producto", mappedBy="producto_tipo", cascade={"persist"})
     */
    protected $productos;

    ...
}

Nuestra finalidad es recuperar todos los productos de una categoría, pero únicamente de un tipo determinado. En el caso de que quisiéramos recuperar todos los productos de una categoría determinada, ésto sería bastante sencillo:


class CategoriaController extends Controller
{
    public function indexAction()
    {
        $categoria = $this->getDoctrine()
            ->getRepository('VabadusBundle:Categoria')
            ->find(1);
 
        $productos = $categoria->getProductos();
 
        ...
    }
}

Pero nuestro objetivo es mostrar todos los productos de la categoría Jamones, pero de un tipo en particular, deshuesado, loncheado,... Es decir, tenemos que encontrar la forma de poder filtrar el resultado que nos devuelve $categoría->getProductos().

Quizás la primera opción que nos vendrá a la cabeza será crear un método en el repositorio donde construyamos una consulta para recuperar los productos de una categoría, que son de un tipo en particular:


public function getProductosByCategoriaYTipo($categoriaId, $productoTipoId)
{
    $qb = $this->createQueryBuilder('p')
        ->innerJoin('p.categoria', 'c')
        ->innerJoin('p.productoTipo', 'pt')
        ->where('c.id = :categoriaId')
        ->andWhere('pt.id = :productoTipoId')
        ->setParameter('categoriaId', $categoriaId)
        ->setParameter('productoTipoId', $productoTipoId)
    ;
 
    return $qb->getQuery()->getResult();
}

Esto podría resolver nuestro problema, pero no parece la mejor opción ya que lanzaríamos una consulta que resulta innecesaria si ya hemos recuperado todos los productos con $categoría->getProductos(). Por tanto, tendríamos que encontrar una forma de filtrar todos los productos, que ya hemos recuperado, de una determinada categoría. Para ello, usando Doctrine tenemos el filtrado de colecciones, una API que nos permitirá dividir parte de los datos de una colección. Si la colección todavía no se ha cargado desde la base de datos, la API de filtrado permite trabajar a nivel de SQL, optimizando el acceso a grandes colecciones.

Usando por tanto la clase Criteria que nos ofrece Doctrine, definimos el siguiente método en la clase de la entidad Categoria:


use Doctrine\Common\Collections\Criteria;

class Categoria
{
    ...

    public function getProductosByProductoTipo($productoTipoId)
    {
        $criteria = Criteria::create();
        $criteria->where(Criteria::expr()->eq('productoTipo', $productoTipoId));
        
        return $this->productos->matching($criteria);
    }

    ...
}

Hecho esto, podemos volver al controlador y utilizar ya este nuevo método que nos devuelva solo aquellos productos de una categoría y de un tipo determinado:


class CategoriaController extends Controller
{
    public function indexAction()
    {
        $categoria = $this->getDoctrine()
            ->getRepository('VabadusBundle:Categoria')
            ->find(1)
        ;
 
        $productos = $categoria->getProductosByProductoTipo(5);
 
        ...
    }
} 

Como podrás ver si revisas la documentación de Doctrine referente al filtrado de colecciones, la clase Criteria permite construir expresiones a través del ExpressionBuilder, tales como eq($field, $value), gt($field, $value), isNull($field), in($field, array $values), etc.



ARTÍCULOS RELACIONADOS