Постраничный вывод
Тысячу статей на одной странице не показывают: это медленно и неудобно. Записи выводят порциями, а внизу ставят ссылки на соседние страницы. В Doctrine для этого есть класс Paginator.
Запрос одной страницы
// src/Repository/PostRepository.php
use Doctrine\ORM\Tools\Pagination\Paginator;
public function paginate(int $page, int $perPage = 10): Paginator
{
$query = $this->createQueryBuilder('p')
->orderBy('p.id', 'DESC')
->setFirstResult(($page - 1) * $perPage)
->setMaxResults($perPage)
->getQuery();
return new Paginator($query);
}setFirstResult() — сколько записей пропустить, setMaxResults() — сколько взять. Paginator дополнительно считает общее количество.
Контроллер
Номер страницы берут из адреса — /blog?page=2:
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
#[Route('/blog', name: 'blog_index')]
public function index(PostRepository $posts, #[MapQueryParameter] int $page = 1): Response
{
$page = max(1, $page);
$perPage = 10;
$paginator = $posts->paginate($page, $perPage);
return $this->render('blog/index.html.twig', [
'posts' => $paginator,
'page' => $page,
'pages' => (int) ceil(count($paginator) / $perPage),
]);
}count($paginator) возвращает число всех записей, а не только на текущей странице.
Шаблон
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% endfor %}
<nav>
{% if page > 1 %}
<a href="{{ path('blog_index', {page: page - 1}) }}">← Назад</a>
{% endif %}
Страница {{ page }} из {{ pages }}
{% if page < pages %}
<a href="{{ path('blog_index', {page: page + 1}) }}">Вперёд →</a>
{% endif %}
</nav>Параметр, которого нет в адресе маршрута, path() добавляет после знака ?.
Готовый пакет
KnpPaginatorBundle берёт расчёты и вывод ссылок на себя:
composer require knplabs/knp-paginator-bundleuse Knp\Component\Pager\PaginatorInterface;
#[Route('/blog', name: 'blog_index')]
public function index(
PostRepository $posts,
PaginatorInterface $paginator,
#[MapQueryParameter] int $page = 1,
): Response {
$query = $posts->createQueryBuilder('p')->orderBy('p.id', 'DESC');
return $this->render('blog/index.html.twig', [
'posts' => $paginator->paginate($query, $page, 10),
]);
}{% for post in posts %}
<h2>{{ post.title }}</h2>
{% endfor %}
{{ knp_pagination_render(posts) }}Сохранение фильтров
Если на странице есть поиск, его параметры передают в ссылки, иначе на второй странице фильтр пропадёт:
<a href="{{ path('blog_index', {page: page + 1, q: app.request.query.get('q')}) }}">Вперёд →</a>Частая ошибка
Номер страницы приходит от посетителя. При page=0 или page=-5 смещение станет отрицательным, и база вернёт ошибку. Ограничивайте значение — max(1, $page) — и не забывайте сортировку: без orderBy() порядок записей не определён, и одна запись может попасть на две страницы.