This course is still being released! Check back later for more chapters.
Paginación
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeFoundry nos ayudó a añadir 20 naves. Eso hace que nuestra aplicación parezca más realista. Pero en producción, podríamos tener miles de naves estelares. Esta página sería gigantesca e inutilizable. Probablemente también tardaría mucho en cargarse, ¡tiempo durante el cual es probable que nos asimilen!
¿La solución? Paginar los resultados: mostrar unos pocos cada vez, o por página.
Instalar Pagerfanta
Para ello, utilizaremos una biblioteca llamada Pagerfanta -¡qué nombre más chulo! Es una biblioteca de paginación genérica, ¡pero tiene una gran integración con Doctrine! Añade los dos paquetes necesarios:
composer require babdev/pagerfanta-bundle pagerfanta/doctrine-orm-adapter
Desplázate hacia arriba para ver lo que instala. pagerfanta/doctrine-orm-adapter
es el pegamento entre Pagerfanta y Doctrine.
Paginar una consulta
En nuestra página de inicio, estamos utilizando findIncomplete()
de StarshipRepository
. Ábrelo y busca el método. Cambia el tipo de retorno a Pagerfanta
: un objeto con superpoderes relacionados con la paginación. Pero puedes hacer un bucle sobre este objeto como si fuera un array, así que deja el docblock como está:
// ... lines 1 - 14 | |
class StarshipRepository extends ServiceEntityRepository | |
{ | |
// ... lines 17 - 21 | |
/** | |
* @return Starship[] | |
*/ | |
public function findIncomplete(): Pagerfanta | |
{ | |
// ... lines 27 - 34 | |
} | |
// ... lines 36 - 65 | |
} |
Ahora, una cosa superimportante que hay que recordar al paginar una consulta es tener un orden predecible. Añade ->orderBy('e.arrivedAt', 'DESC')
:
// ... lines 1 - 14 | |
class StarshipRepository extends ServiceEntityRepository | |
{ | |
// ... lines 17 - 24 | |
public function findIncomplete(): Pagerfanta | |
// ... lines 26 - 28 | |
->orderBy('s.arrivedAt', 'DESC') | |
// ... lines 30 - 34 | |
} | |
// ... lines 36 - 65 | |
} |
Pero en lugar de devolver, añade esto a una variable llamada $query
, luego eliminagetResult()
: nuestro trabajo cambia de ejecutar la consulta a simplemente construirla. Pagerfanta se encargará de la ejecución real. Devuelvenew Pagerfanta(new QueryAdapter($query))
y asegúrate de importar estas dos clases:
// ... lines 1 - 14 | |
class StarshipRepository extends ServiceEntityRepository | |
{ | |
// ... lines 17 - 24 | |
public function findIncomplete(): Pagerfanta | |
// ... line 26 | |
$query = $this->createQueryBuilder('s') | |
// ... lines 28 - 30 | |
->getQuery() | |
; | |
// ... line 33 | |
return new Pagerfanta(new QueryAdapter($query)); | |
} | |
// ... lines 36 - 65 | |
} |
Configurar la página
De vuelta a MainController
, $ship
es ahora un objeto Pagerfanta
. Para utilizarlo, tenemos que decirle 2 cosas: cuántas naves queremos en cada página - $ships->setMaxPerPage(5)
- y en qué página se encuentra actualmente el usuario: utiliza $ships->setCurrentPage(1)
por ahora. Ah, y asegúrate de llamar a setCurrentPage()
después de setMaxPerPage()
o sucederán cosas raras de viajes en el tiempo:
// ... lines 1 - 12 | |
public function homepage( | |
// ... line 14 | |
): Response { | |
$ships = $repository->findIncomplete(); | |
$ships->setMaxPerPage(5); | |
$ships->setCurrentPage(1); | |
// ... lines 19 - 25 | |
} | |
} |
Muévete... actualiza... ¡y mira! Sólo mostramos 5 elementos: la primera página.
Vuelve a cambiar a setCurrentPage(2)
:
// ... lines 1 - 12 | |
public function homepage( | |
// ... line 14 | |
): Response { | |
// ... lines 16 - 17 | |
$ships->setCurrentPage(2); | |
// ... lines 19 - 25 | |
} | |
} |
y actualiza de nuevo.
Todavía 5 barcos, pero barcos diferentes: la segunda página. Echemos un vistazo a la consulta. ¡Hay varias! Una para contar el número total de resultados y otra para obtener sólo los de esta página. Muy chulo.
En lugar de codificar la página en 1 ó 2 -una solución temporal y poco convincente-, leámosla dinámicamente desde la URL, como con?page=1
o ?page=2
.
Página actual desde la petición
Para ello, autocodifica Request $request
-la de HttpFoundation
- y cambia el argumento setCurrentPage()
por $request->query->get('page',
1)para leer ese valor y poner por defecto 1 si falta:
// ... lines 1 - 10 | |
class MainController extends AbstractController | |
{ | |
// ... line 13 | |
public function homepage( | |
// ... line 15 | |
Request $request, | |
): Response { | |
// ... lines 18 - 19 | |
$ships->setCurrentPage($request->query->get('page', 1)); | |
// ... lines 21 - 27 | |
} | |
} |
Vuelve y actualiza. Esta es la página 1 porque no hay parámetro page
. Añade ?page=2
a la URL y... ¡estamos en la página 2!
Vale, ¿qué más estaría bien? ¿Qué tal mostrar el número total de naves, el número total de páginas y el número de la página actual?
Mostrar información de paginación
De vuelta en el controlador, Cmd + Click homepage.html.twig
para abrirlo.
Pon esta información debajo de <h1>
. Cambiaré el margen inferior y añadiré un nuevo <div>
(con un poco de estilo). Dentro, escribe {{ ships.nbResults }}
. Luego: Página {{ ships.currentPage }}
de {{ ships.nbPages }}
:
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
<h1 class="text-4xl font-semibold mb-3"> | |
// ... line 11 | |
</h1> | |
// ... line 13 | |
<div class="text-slate-400 mb-4"> | |
{{ ships.nbResults }} ships (Page {{ ships.currentPage }} of {{ ships.nbPages }}) | |
</div> | |
// ... lines 17 - 57 | |
</div> | |
</main> | |
{% endblock %} |
Vuelve a girar y actualiza. ¡Perfecto! Tenemos 14 naves incompletas en total, y estamos en la página 1 de 3. Tus números pueden variar dependiendo de cuántas de tus 20 naves tengan el estado incompleto al azar.
Enlaces de paginación
¡Vale! ¿Qué falta? ¿Qué tal unos enlaces para navegar entre páginas? Debajo de la lista, voy a pegar algo de código. Primero,if ships.haveToPaginate
: no se necesitan enlaces si sólo hay una página. Después,if ships.hasPreviousPage
, vamos a añadir un enlace a la página anterior si existe, no habría página anterior si estamos en la página 1. Dentro, genera una URL a esta página: app_homepage
. Pero pasa un parámetro: page
ajustado a ships.getPreviousPage
. Como page
no está definido en la ruta, se añadirá como parámetro de consulta page
. ¡Eso es exactamente lo que queremos! Repítelo para el enlace Next
: si ships.hasNextPage
y ships.getNextPage
:
// ... lines 1 - 4 | |
{% block body %} | |
<main class="flex flex-col lg:flex-row"> | |
// ... lines 7 - 8 | |
<div class="px-12 pt-10 w-full"> | |
// ... lines 10 - 51 | |
</div> | |
// ... line 53 | |
{% if ships.haveToPaginate %} | |
<div class="flex justify-around mt-3 underline font-semibold"> | |
{% if ships.hasPreviousPage %} | |
<a href="{{ path('app_homepage', {page: ships.getPreviousPage}) }}">< Previous</a> | |
{% endif %} | |
{% if ships.hasNextPage %} | |
<a href="{{ path('app_homepage', {page: ships.getNextPage}) }}">Next ></a> | |
{% endif %} | |
</div> | |
{% endif %} | |
// ... lines 64 - 68 | |
</div> | |
</main> | |
{% endblock %} |
Actualiza, desplázate hacia abajo y ¡listo! ¡Vemos un enlace Next
! Haz clic en él... y ahora estamos en la página 2 de 3, y la URL tiene ?page=2
. Abajo, nuestro widget tiene enlaces Previous
y Next
. Vuelve a hacer clic en Next
... página 3 de 3, y luego en Previous
, de vuelta a la página 2 de 3. ¡Perfección de la paginación!
Construimos estos enlaces a mano, lo que nos da un poder de personalización ilimitado. Pero Pagerfanta sí que puede generar esto por nosotros. Si quieres ver cómo, consulta la documentación de Pagerfanta. El inconveniente es que personalizar el HTML es un poco más difícil.
A continuación, vamos a añadir más campos a nuestra entidad Starship
. ¿Lo mejor? Ver lo fácil que es añadir esa columna a la base de datos. ¡Vamos a hacerlo!