Постраничный вывод
Когда статей становится сотни, выводить их одним списком нельзя: страница грузится долго, а читать её неудобно. Постраничный вывод (пагинация) показывает по несколько записей и ссылки «назад» и «вперёд». В Laminas этим занимается пакет laminas-paginator.
Установка
composer require laminas/laminas-paginator laminas/laminas-paginator-adapter-laminasdbПервый пакет — сам разбиватель на страницы, второй — адаптер, который умеет брать данные запросом laminas-db. Согласитесь вписать модуль Laminas\Paginator в config/modules.config.php.
Как работает Paginator
Объекту Paginator дают источник данных, размер страницы и номер текущей страницы. Простейший источник — массив:
use Laminas\Paginator\Adapter\ArrayAdapter;
use Laminas\Paginator\Paginator;
$paginator = new Paginator(new ArrayAdapter(range(1, 25)));
$paginator->setItemCountPerPage(10);
$paginator->setCurrentPageNumber(2);
foreach ($paginator as $number) {
echo $number . ' ';
}Цикл выведет числа от 11 до 20 — вторую страницу из трёх. Вызов count($paginator) возвращает число страниц.
Страницы из базы
Для таблицы используют адаптер DbSelect: он выполняет запрос с LIMIT и отдельно считает общее число строк. Добавьте метод в module/Blog/src/Model/PostTable.php:
// module/Blog/src/Model/PostTable.php
use Laminas\Paginator\Adapter\LaminasDb\DbSelect;
use Laminas\Paginator\Paginator;
public function paginate(int $page, int $perPage = 10): Paginator
{
$select = $this->gateway->getSql()->select();
$select->order('id DESC');
$adapter = new DbSelect(
$select,
$this->gateway->getAdapter(),
$this->gateway->getResultSetPrototype(),
);
$paginator = new Paginator($adapter);
$paginator->setItemCountPerPage($perPage);
$paginator->setCurrentPageNumber($page);
return $paginator;
}Третий аргумент DbSelect — образец результата: благодаря ему строки приходят объектами Post, как в остальных методах таблицы.
Контроллер
// module/Blog/src/Controller/PostController.php
public function indexAction()
{
$page = max(1, (int) $this->params()->fromQuery('page', 1));
return new ViewModel([
'paginator' => $this->posts->paginate($page, 5),
]);
}Номер страницы берётся из строки запроса: /blog?page=2.
Шаблон списка
<!-- module/Blog/view/blog/post/index.phtml -->
<ul>
<?php foreach ($this->paginator as $post): ?>
<li><?= $this->escapeHtml($post->title) ?></li>
<?php endforeach; ?>
</ul>
<?= $this->paginationControl($this->paginator, 'Sliding', 'partial/paginator', ['route' => 'blog']) ?>paginationControl() выводит ссылки с помощью отдельной части шаблона. Sliding — способ выбора номеров: показываются страницы вокруг текущей.
Шаблон ссылок
Создайте файл module/Blog/view/partial/paginator.phtml:
<!-- module/Blog/view/partial/paginator.phtml -->
<?php if ($this->pageCount > 1): ?>
<nav>
<?php if (isset($this->previous)): ?>
<a href="<?= $this->url($this->route, [], ['query' => ['page' => $this->previous]]) ?>">Назад</a>
<?php endif; ?>
<?php foreach ($this->pagesInRange as $page): ?>
<?php if ($page === $this->current): ?>
<strong><?= $page ?></strong>
<?php else: ?>
<a href="<?= $this->url($this->route, [], ['query' => ['page' => $page]]) ?>"><?= $page ?></a>
<?php endif; ?>
<?php endforeach; ?>
<?php if (isset($this->next)): ?>
<a href="<?= $this->url($this->route, [], ['query' => ['page' => $this->next]]) ?>">Вперёд</a>
<?php endif; ?>
</nav>
<?php endif; ?>Переменные pageCount, current, previous, next и pagesInRange передаёт сам помощник, а route — это четвёртый аргумент paginationControl(). Откройте /blog: под списком из пяти статей появятся номера страниц.
Частая ошибка
Загрузить все строки методом fetchAll() и разбить их ArrayAdapter — значит каждый раз читать из базы всю таблицу ради пяти записей. Для данных из базы используйте DbSelect: он запрашивает только строки текущей страницы.