Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Al final, esta página se va a hacer superlarga. Cuando tengamos mil mezclas, ¡probablemente ni siquiera se cargue! Podemos solucionarlo añadiendo la paginación. ¿Doctrine tiene la capacidad de paginar los resultados? Sí, la tiene Aunque yo suelo instalar otra biblioteca que añade más funciones además de las de Doctrine.

Busca tu terminal y ejecuta:

composer require babdev/pagerfanta-bundle pagerfanta/doctrine-orm-adapter

Esto instala un bundle de Pagerfanta, que es una envoltura de una biblioteca realmente buena llamada Pagerfanta. Pagerfanta puede paginar muchas cosas, como los resultados de Doctrine, los resultados de Elasticsearch y mucho más. También instalamos su adaptador ORM de Doctrine, que nos dará todo lo que necesitamos para paginar nuestros resultados de Doctrine. En este caso, cuando ejecutamos

git status

añadió un bundle, pero la receta no necesitó hacer nada más. ¡Genial! Entonces, ¿cómo funciona esta biblioteca?

Abre src/Controller/VinylController y busca la acción browse(). En lugar de consultar todas las mezclas, como estamos haciendo ahora, vamos a decirle a la biblioteca Pagerfanta en qué página se encuentra el usuario, cuántos resultados debe mostrar por página, y entonces nos consultará los resultados correctos.

Devolver un QueryBuilder

Para que esto funcione, en lugar de llamar a findAllOrderedByVotes() y obtener todos los resultados, tenemos que llamar a un método de nuestro repositorio que devuelva un QueryBuilder. Abre src/Repository/VinylMixRepository y desplázate hastafindAllOrderedByVotes(). Por el momento sólo estamos utilizando este método, así que cámbiale el nombre a createOrderedByVotesQueryBuilder()... y esto devolverá ahora un QueryBuilder - el de Doctrine ORM. Eliminaré la documentación de PHP en la parte superior... y lo único que tenemos que hacer aquí abajo es eliminargetQuery() y getResult() para que sólo devolvamos $queryBuilder.

... lines 1 - 6
use Doctrine\ORM\QueryBuilder;
... lines 8 - 17
class VinylMixRepository extends ServiceEntityRepository
{
... lines 20 - 42
public function createOrderedByVotesQueryBuilder(string $genre = null): QueryBuilder
{
... lines 45 - 51
return $queryBuilder;
}
... lines 54 - 70
}

En VinylController, cambia esto por$queryBuilder = $mixRepository->createOrderedByVotesQueryBuilder($slug)

... lines 1 - 12
class VinylController extends AbstractController
{
... lines 15 - 38
public function browse(VinylMixRepository $mixRepository, string $slug = null): Response
{
... lines 41 - 42
$queryBuilder = $mixRepository->createOrderedByVotesQueryBuilder($slug);
... lines 44 - 54
}
}

Inicializar Pagerfanta son dos líneas. Primero, crea el adaptador -$adapter = new QueryAdapter() y pásale $queryBuilder. Luego crea el objeto Pagerfanta con$pagerfanta = Pagerfanta::createForCurrentPageWithMaxPerPage()

Esto es un bocado. Pásale el $adapter, la página actual - en este momento, voy a codificar en duro 1 - y finalmente el máximo de resultados por página que queremos. Vamos a utilizar 9 ya que nuestras mezclas aparecen en tres columnas.

... lines 1 - 5
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
... lines 8 - 12
class VinylController extends AbstractController
{
... lines 15 - 38
public function browse(VinylMixRepository $mixRepository, string $slug = null): Response
{
... lines 41 - 43
$adapter = new QueryAdapter($queryBuilder);
$pagerfanta = Pagerfanta::createForCurrentPageWithMaxPerPage(
$adapter,
1,
9
);
... lines 50 - 54
}
}

Ahora que tenemos este objeto Pagerfanta, vamos a pasarlo a la plantilla en lugar de mixes. Sustitúyelo por una nueva variable llamada pager ajustada a $pagerfanta.

... lines 1 - 38
public function browse(VinylMixRepository $mixRepository, string $slug = null): Response
{
... lines 41 - 50
return $this->render('vinyl/browse.html.twig', [
... line 52
'pager' => $pagerfanta,
]);
}
... lines 56 - 57

Lo bueno de este objeto $pagerfanta es que puedes hacer un bucle sobre él. Y en cuanto lo hagas, ejecutará la consulta correcta para obtener sólo los resultados de esta página. En templates/vinyl/browse.html.twig, en lugar de {% for mix in mixes %}, di{% for mix in pager %}.

... lines 1 - 2
{% block body %}
... lines 4 - 27
<div class="row">
{% for mix in pager %}
... lines 30 - 44
{% endfor %}
</div>
... lines 47 - 48
{% endblock %}

Eso es todo. Cada resultado del bucle seguirá siendo un objeto VinylMix.

Si vamos y recargamos... ¡lo tenemos! Muestra nueve resultados: ¡los resultados de la página 1!

Enlace a la página siguiente

Lo que necesitamos ahora son enlaces a las páginas siguientes y anteriores... y esta biblioteca también puede ayudarnos con eso. De vuelta a tu terminal, ejecuta:

composer require pagerfanta/twig

Una de las cosas más complicadas de la biblioteca Pagerfanta es que, en lugar de ser una biblioteca gigante que tiene todo lo que necesitas, está dividida en un montón de bibliotecas más pequeñas. Así que si quieres el soporte del adaptador ORM, tienes que instalarlo como hemos hecho antes. Si quieres el soporte de Twig para añadir enlaces, también tienes que instalarlo. Sin embargo, una vez que lo hagas, es bastante sencillo.

De vuelta a nuestra plantilla, busca el objeto {% endfor %}, y justo después, di{{ pagerfanta() }}, pasándole el objeto pager.

... lines 1 - 2
{% block body %}
... lines 4 - 26
<h2 class="mt-5">Mixes</h2>
<div class="row">
... lines 29 - 46
{{ pagerfanta(pager) }}
</div>
... lines 49 - 50
{% endblock %}

¡Compruébalo! Cuando actualizamos... ¡tenemos enlaces en la parte inferior! Son... feos, pero lo arreglaremos en un momento.

Leer la página actual

Si haces clic en el enlace "Siguiente", arriba en nuestra URL, vemos ?page=2. Aunque... los resultados no cambian realmente. Seguimos viendo los mismos resultados de la página 1. Y... eso tiene sentido. Recuerda que en VinylController, codifiqué la página actual en 1. Así que, aunque tengamos ?page=2 aquí arriba, Pagerfanta sigue pensando que estamos en la Página 1.

Lo que tenemos que hacer es leer este parámetro de consulta y pasarlo como este segundo argumento ¡No hay problema! ¿Cómo leemos los parámetros de consulta? Bueno, es información de la petición, así que necesitamos el objeto Request.

Justo antes de nuestro argumento opcional, añade un nuevo argumento $request de tipoRequest: el de HttpFoundation. Ahora, aquí abajo, en lugar de 1, di $request->query (así es como se obtienen los parámetros de consulta), con->get('page')... y por defecto esto es 1 si no hay ?page= en la URL.

... lines 1 - 8
use Symfony\Component\HttpFoundation\Request;
... lines 10 - 13
class VinylController extends AbstractController
{
... lines 16 - 39
public function browse(VinylMixRepository $mixRepository, Request $request, string $slug = null): Response
{
... lines 42 - 45
$pagerfanta = Pagerfanta::createForCurrentPageWithMaxPerPage(
... line 47
$request->query->get('page', 1),
... line 49
);
... lines 51 - 55
}
}

Por cierto, si quieres, también puedes añadir {page} aquí arriba. De este modo, Pagerfanta pondrá automáticamente el número de página dentro de la URL en lugar de establecerlo como parámetro de consulta.

Si nos dirigimos y refrescamos... ahora mismo, tenemos ?page=2. Aquí abajo... ¡sabe que estamos en la página 2! Si vamos a la siguiente página... ¡sí! ¡Vemos un conjunto diferente de resultados!

Estilizando los enlaces de paginación

Aunque, esto sigue siendo súper feo. Afortunadamente, el bundle nos da una forma de controlar el marcado que se utiliza para los enlaces de paginación. E incluso viene con soporte automático para el marcado compatible con CSS de Bootstrap. Sólo tenemos que decirle al bundle que lo utilice.

Así que... tenemos que configurar el bundle. Pero... el bundle no nos ha dado ningún archivo de configuración nuevo cuando se ha instalado. No pasa nada No todos los bundles nuevos nos dan archivos de configuración. Pero en cuanto necesites uno, ¡crea uno! Como este bundle se llamaBabdevPagerfantaBundle, voy a crear un nuevo archivo llamadobabdev_pagerfanta.yaml. Como aprendimos en el último tutorial, el nombre de estos archivos no es importante. Lo importante es la clave raíz, que debe serbabdev_pagerfanta. Para cambiar la forma en que se muestra la paginación, añade default_view: twig y luego default_twig_template a @BabDevPagerfanta/twitter_bootstrap5.html.twig.

babdev_pagerfanta:
default_view: twig
default_twig_template: '@BabDevPagerfanta/twitter_bootstrap5.html.twig'

Como con cualquier otra configuración, no hay forma de que sepas que ésta es la configuración correcta simplemente adivinando. Tienes que consultar la documentación.

Si volvemos y actualizamos... eh, no ha cambiado nada. Este es un pequeño error que a veces se encuentra en Symfony cuando se crea un nuevo archivo de configuración. Symfony no se dio cuenta... y por eso no sabía que tenía que reconstruir su caché. Esta es una situación súper rara, pero si alguna vez crees que puede estar ocurriendo, es bastante fácil borrar manualmente la caché ejecutando:

php bin/console cache:clear

Y... oh... explota. Seguramente te habrás dado cuenta de por qué. ¡Me encanta este error!

No hay ninguna extensión capaz de cargar la configuración para "baberdev_pagerfanta"

Se supone que es babdev_pagerfanta. ¡Ups! Y ahora... ¡perfecto! Está contento. Y cuando refrescamos... ¡lo ve! En un proyecto real, probablemente querremos añadir algo de CSS adicional para hacer este "modo oscuro"... pero ya lo tenemos.

Bien, equipo, ¡ya hemos terminado! Como extra, vamos a refactorizar esta paginación para convertirla en un scroll eterno impulsado por JavaScript... ¡excepto el giro argumental! Vamos a hacerlo sin escribir una sola línea de JavaScript. Eso a continuación.

Leave a comment!

10
Login or Register to join the conversation

Thanks for this awesome tutorial. I am wondering if there is any way to adapte Pagerfanta with Twig orders. (I know how to custmize it by QueryBuilder)
For example:
Let's say I need to use "If statements" like this:

{% for activity in pager %}  
	{% if activity.published == true %}
		bla bla bla ...
	{% endif %}
{% endfor %}

In this case, Twig still counts the non-published activities, it only hides it.

Many thanks in advance!

Reply

Hi @Lubna!

Interesting! You want to hide certain results, but still want them to be counted in the total item count and also in the pagination? What I mean is, if you are showing 10 items per page, and there are 100 items, but only 50 are published, you would still want Pagerfanta to show 10 pages. Is that correct?

If so, I might be missing some detail. The code you showed above looks great, of course: you are able to loop over the 10 items on the page and use an if statement to hide (for example) 5 of them. Then, naturally, when you use pagerfanta, it will not know about this, so it will still think there are 10 pages. I'm guessing this isn't quite what you want - but let me know - I'm missing a piece to your requirement!

Sorry I couldn't be more immediately helpful - but let me know what detail I'm missing :).

Cheers!

1 Reply

Thanks @weaverryan for your reply and appreciate your time!

Not exactly, when I'm showing 10 items per page, and there are 100 items, but only 50 are published, Pagerfanta still show 10 pages.

Seems there is no solution, because Pagerfanta can't control the data reicieved of QuieryBuilder.
Have a nice day :)

Reply

Hi @Lubna!

I understand! Yes, the solution for this would always need to be to "go back and modify the original query" - which I know you were already aware of :).

Good luck!

1 Reply
skocdopolet Avatar
skocdopolet Avatar skocdopolet | posted hace 7 meses

Hey there,

I have on single page two lists of items (for example active and archive messages). I need to have pagination for both of lists. But the parameter page is in collision with each other.

How I can specify for each pagerfanta name of parameter for pagination? For example to have activePage parameter for active messages parameter and archivePage for archived messages? So URL should be .../messages/list?activePage=2&archivePage=4 - this means that active messages are at page 2 and archive messages are at page 4.

Thanks everyone for advices.
Cheers!

Reply

Hey skocdopolet!

That's an excellent question! Here's how to do this, on your template:

{{ pagerfanta(pager, 'default', {'pageParameter': '[activePage]'}) }}

Ref: bottom of https://www.babdev.com/open-source/packages/pagerfantabundle/docs/3.x/rendering-pagerfantas

Then, of course, you'll read $request->query->get('activePage') in your controller when fetching the current page.

I hope this helps!

1 Reply
skocdopolet Avatar

Hey weaverryan!

Thank you for your reply. Its worked for me very well!

I have another question. Is it possible to omit prev and next links while rendering pagerfanta? I need only the page numbers...

I am searching in the documentation and I was try to inspect source code too, but I was not successfull.

I solve this requirement by CSS and display:none, but I am searching better solution.

Cheers!

Reply

Hey skocdopolet!

Yea... interesting question. As best I can see, this is not supported by a simple option. Instead, I think you'll need to override the pagination template. So, for example, in this tutorial we're using the @BabDevPagerfanta/twitter_bootstrap5.html.twig template, which should be this one: https://github.com/BabDev/Pagerfanta/blob/3.x/lib/Twig/templates/twitter_bootstrap5.html.twig... which really just extends this one: https://github.com/BabDev/Pagerfanta/blob/3.x/lib/Twig/templates/twitter_bootstrap4.html.twig

Anyways, I believe if you just copied that template into your code, customized it, then updated the pagerfanta config to point at it, you'd be in business. Specifically, you would override all of the previous_page_link and next_page_link blocks (including the disabled versions) and render nothing.

I'd love to know if this works, so let me know if you have a chance to try it!

Cheers!

2 Reply
ssi-anik Avatar

You can use ($page = intval($request->query->get('page', 1))) > 0 ? $page : 1 to make sure that page always an integer otherwise the first page. Because the type for the parameter $currentPage (the second argument) is set to int and it'll break your code and throw 500.

Reply

Hey there,

Yes, is a good idea to make sure that you get an int out of the request param, and there's a new fancy way to do that in a modern Symfony app
$request->query->getInt('page', 1);

Cheers!

3 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.7", // v3.7.0
        "doctrine/doctrine-bundle": "^2.7", // 2.7.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.12", // 2.12.3
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.2", // v6.2.6
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.2
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.2
        "symfony/framework-bundle": "6.1.*", // v6.1.2
        "symfony/http-client": "6.1.*", // v6.1.2
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
        "symfony/runtime": "6.1.*", // v6.1.1
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/ux-turbo": "^2.0", // v2.3.0
        "symfony/webpack-encore-bundle": "^1.13", // v1.15.1
        "symfony/yaml": "6.1.*", // v6.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.1.*", // v6.1.0
        "symfony/maker-bundle": "^1.41", // v1.44.0
        "symfony/stopwatch": "6.1.*", // v6.1.0
        "symfony/web-profiler-bundle": "6.1.*", // v6.1.2
        "zenstruck/foundry": "^1.21" // v1.21.0
    }
}
userVoice