Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Постраничный вывод

Тысячу статей на одной странице не показывают: это медленно и неудобно. Записи выводят порциями, а внизу ставят ссылки на соседние страницы. В 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-bundle
use 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() порядок записей не определён, и одна запись может попасть на две страницы.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.