
Paginando en symfony
La paginación de resultados es un requisito común en proyectos web, pero su implementación puede volverse compleja rápidamente. Aunque Symfony cuenta con bundles como knp-paginator-bundle, en ocasiones necesitamos una solución más personalizada. Te presento una implementación que extiende el paginador de Doctrine, ofreciendo metadatos enriquecidos para su uso en vistas y APIs.
<?php use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use ArrayIterator; class EnhancedPaginator extends DoctrinePaginator { public const DEFAULT_ITEMS_PER_PAGE = 9; private int $totalItems; private array $currentPageItems; private int $nbPages; private int $currentPage; public function __construct( QueryBuilder|Query $query, int $page = 1, int $itemsPerPage = self::DEFAULT_ITEMS_PER_PAGE, bool $fetchJoinCollection = true ) { $this->currentPage = max(1, $page); $offset = ($this->currentPage - 1) * $itemsPerPage; $query->setFirstResult($offset) ->setMaxResults($itemsPerPage); parent::__construct($query, $fetchJoinCollection); $this->totalItems = parent::count(); $this->currentPageItems = iterator_to_array(parent::getIterator()); $this->nbPages = $this->calculateTotalPages($itemsPerPage); } private function calculateTotalPages(int $itemsPerPage): int { if ($itemsPerPage < 1) return 0; return (int) ceil($this->totalItems / $itemsPerPage); } // Métodos de acceso a la información de paginación public function getTotalItems(): int { return $this->totalItems; } public function getItemsPerPage(): int { return $this->getQuery()->getMaxResults() ?? self::DEFAULT_ITEMS_PER_PAGE; } public function getCurrentPage(): int { return $this->currentPage; } public function getTotalPages(): int { return $this->nbPages; } public function hasNextPage(): bool { return $this->currentPage < $this->nbPages; } public function hasPreviousPage(): bool { return $this->currentPage > 1; } public function getResults(): array { return $this->currentPageItems; } public function getPaginationData(): array { return [ 'results' => $this->getResults(), 'pagination' => [ 'total_items' => $this->getTotalItems(), 'items_per_page' => $this->getItemsPerPage(), 'current_page' => $this->getCurrentPage(), 'total_pages' => $this->getTotalPages(), 'has_previous_page' => $this->hasPreviousPage(), 'has_next_page' => $this->hasNextPage(), 'previous_page' => $this->hasPreviousPage() ? $this->currentPage - 1 : null, 'next_page' => $this->hasNextPage() ? $this->currentPage + 1 : null, ] ]; } public function getIterator(): ArrayIterator { return new ArrayIterator($this->getPaginationData()); } }
Principales Mejoras y Características
Configuración Flexible de Items por Página
// Uso básico $paginator = new EnhancedPaginator($query, $page); // Personalización $paginator = new EnhancedPaginator($query, $page, 15);
Lógica de Paginación Optimizada
- Cálculo seguro de offsets
- Validación automática de números de página
- Métodos simplificados para navegación
Metadatos Enriquecidos
{ "results": [...], "pagination": { "total_items": 45, "items_per_page": 9, "current_page": 2, "total_pages": 5, "has_previous_page": true, "has_next_page": true, "previous_page": 1, "next_page": 3 } }
Manejo de Errores Mejorado
- Protección contra valores negativos en páginas
- Cálculo seguro de total de páginas
Cómo Usarlo en tu Proyecto
En tu repositorio o servicio:
public function findPaginated(int $page, int $itemsPerPage = 10): EnhancedPaginator { $qb = $this->createQueryBuilder('p') ->orderBy('p.createdAt', 'DESC'); return new EnhancedPaginator($qb->getQuery(), $page, $itemsPerPage); }
En tu controlador:
public function listProducts(Request $request): JsonResponse { $page = $request->query->getInt('page', 1); $paginator = $this->productRepository->findPaginated($page); return $this->json($paginator->getPaginationData()); }
En tu plantilla Twig:
{% for product in paginator.results %} {{ product.name }} {% endfor %} <nav> {% if paginator.pagination.has_previous_page %} <a href="?page={{ paginator.pagination.previous_page }}">Anterior</a> {% endif %} Página {{ paginator.pagination.current_page }} de {{ paginator.pagination.total_pages }} {% if paginator.pagination.has_next_page %} <a href="?page={{ paginator.pagination.next_page }}">Siguiente</a> {% endif %} </nav>
Ventajas Clave
Encapsulamiento Completo
- Toda la lógica de paginación está contenida en la clase
- Fácil de testear y reutilizar
Interfaz Intuitiva
- Métodos claramente nombrados
- Estructura de datos consistente
Extensible
- Fácil de añadir nuevos métodos (ej. saltar a página específica)
- Adaptable a diferentes necesidades de paginación
Posibles Mejoras Futuras
- Implementar caché para conteos de resultados
- Añadir soporte para diferentes estrategias de paginación
- Integrar validación avanzada de parámetros
- Agregar métodos para rangos de páginas
Este enfoque proporciona una solución robusta y mantenible para la paginación, especialmente útil cuando necesitamos información detallada para construir interfaces de usuario complejas o APIs RESTful bien documentadas.
Deja un comentario: