Usar Doctrine con Zend Paginator

Una solución, una idea, un pequeño inicio para usar Doctrine 2 conjuntamente con Zend Paginator y que se entiendan. Sin muchas complicaciones, o tal vez alguna que otra, pero no muchas 😂

Usar Doctrine junto con Zend Paginator

Si usas Zend Framework (o sus componentes) en tus proyectos, te habrás acostumbrado al uso del componente Zend Paginator. No solo eso, sino que, te habrás acostumbrado a su potencia a la hora de crear la páginación. Un componente muy sencillo y fácil de usar.

Seguro que has topado más de una vez con Doctrine 2, y por curiosidad, simple curiosidad, le habrás echado un ojo para ver de que va, y…. 😒 te habrá echado para atrás, porque lo considerabas complicado. Pero como no te rindes tan fácilmente, un día, decides liarte a “golpes” con Doctrine y te das cuenta de que no es para tanto, que está bien y que no es tan complicado.

Pero llega un momento en el que intentas hacer la paginación de una página y te das cuenta de que no va a funcionar con tu querido Zend Paginator. Porque ya te has acostumbrado a el, sabes como funciona y tienes ya hecho varias plantillas para el menú de paginación.

Fusionar Doctrine 2 y Zend Paginator

Después de todo un día dándole vueltas y buscar ideas. He creado mi propia solución para este tema. Mi solución pasa por usar las clases: Doctrine\ORM\QueryBuilder,
Doctrine\ORM\EntityRepository, Zend\Paginator\Adapter\AdapterInterface y por supuesto Zend\Paginator\Paginator.

A la hora de hacer este ejemplo, he seguido la estructura de Zend Framework.

Versiones utilizadas para este ejemplo:

  • PHP: “php”: “^7.1”
  • Doctrine 2: “doctrine/orm”: “^2.6”
  • Zend Paginator: “zendframework/zend-paginator”: “^2.8”

Para empezar es necesario crear una clase que implemente la clase Zend\Paginator\Adapter\AdapterInterface. Esto va hacer que Zend Paginator se integre con Doctrine. El método getItems($offset, $itemCountPerPage) es obligatorio, ya que es el que usa Zend para obtener los items actuales.

<?php
//-- module/Application/src/Paginator/Adapter/Doctrine.php

namespace Application\Paginator\Adapter;

use Doctrine\ORM\QueryBuilder;
use Zend\Paginator\Adapter\AdapterInterface;

class Doctrine implements AdapterInterface
{
    /**
     * Instancia del QueryBuilder de Doctrine.
     *
     * @var QueryBuilder
     */
    protected $doctrine;

    /**
     * Contador total de items.
     *
     * @var int
     */
    protected $rowCount;

    public function __construct(QueryBuilder $query)
    {
        $this->doctrine = $query;
    }

    /**
     * Es la función necesaria del AdapterInterface.
     */
    public function getItems($offset, $itemCountPerPage)
    {
        $qb = clone $this->doctrine;

        return $qb
            ->setFirstResult($offset)
            ->setMaxResults($itemCountPerPage)
            ->getQuery()
            ->getArrayResult()
        ;
    }

    /**
     * Devuelve el número total de filas de la consulta.
     *
     * @return int
     */
    public function count(): int
    {
        if (null !== $this->rowCount)
        {
            return $this->rowCount;
        }

        $this->rowCount = $this->selectCount();

        return $this->rowCount;
    }

    /**
     * Cuenta el número total de filas.
     *
     * @return int
     */
    private function selectCount(): int
    {
        $qb = clone $this->doctrine;

        return $qb->select('COUNT(1) AS C')
            ->setFirstResult(null)
            ->setMaxResults(null)
            ->getQuery()
            ->getSingleScalarResult()
        ;
    }
}

El siguiente archivo es crear una clase base para Doctrine\ORM\EntityRepository extendiendo dicha clase. De esta forma, cuando creamos un repositorio de una entiedad, usamos nuestra clase base si necesitamos que tenga compatibilidad con la paginación. Esta clase base, incluira el método getPaginator(QueryBuilder $query, ?int $page = 1, int $perPage = 25). Es el método que utilizaremos para obetener la paginación de la consulta.

<?php
//-- module/Application/src/Doctrine/ORM/EntityRepository.php

namespace Aplication\Doctrine\ORM;

use Doctrine\ORM\{
    EntityRepository as DoctrineEntityRepository,
    QueryBuilder
};
use Application\Paginator\Adapter\Doctrine as DoctrineAdapter;
use Zend\Paginator\Paginator;

class EntityRepository extends DoctrineEntityRepository
{
    /**
     * Obtener la paginación del resultado result.
     *
     * @param QueryBuilder $query   Es la consulta que se le va a hacer a la base de datos.
     * @param int|null     $page    Número de página actual
     * @param int|null     $perPage Numero de items por página
     *
     * @return Paginator
     */
    public function getPaginator(QueryBuilder $query, ?int $page = 1, int $perPage = 25): Paginator
    {
        $page = max(1, (int) $page);

        $paginator = new Paginator(new DoctrineAdapter($query));
        //- Set current page
        $paginator->setCurrentPageNumber($page);
        //-- Max number of results per page
        $paginator->setItemCountPerPage($perPage);

        return $paginator;
    }
}

Usando el nuevo paginador

<?php
//-- module/Application/src/Controller/Index.php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class Index extends AbstractActionController
{
    public function indexAction()
    {
        $repository = $this->em->getRepository(Entity\Example::class);
        $qb = $repository->createQueryBuilder('u');

        $result = $repository->getPaginator($qb, 1, 25);  
        return [
            'paginator' => $result
        ];
    }
}

Este método lo estoy usando en este proyecto: LoTGD Lo uso para paginar, por ejemplo: los mensajes del día en el archivo “public/motd.php