Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Pagination

When there are hundreds of posts, they cannot be printed as one list: the page loads slowly and is hard to read. Pagination shows a few records at a time plus "previous" and "next" links. In Laminas the laminas-paginator package does this.

Installation

composer require laminas/laminas-paginator laminas/laminas-paginator-adapter-laminasdb

The first package is the paginator itself, the second an adapter that fetches data with a laminas-db query. Agree to inject the Laminas\Paginator module into config/modules.config.php.

How the Paginator works

A Paginator object is given a data source, a page size and the current page number. The simplest source is an array:

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 . ' ';
}

The loop prints the numbers from 11 to 20 — the second page of three. count($paginator) returns the number of pages.

Pages from the database

For a table use the DbSelect adapter: it runs the query with LIMIT and counts the total number of rows separately. Add a method to 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;
}

The third argument of DbSelect is the result prototype: thanks to it rows arrive as Post objects, as in the other table methods.

The controller

// 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),
    ]);
}

The page number comes from the query string: /blog?page=2.

The list view script

<!-- 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() renders the links using a separate partial. Sliding is the way page numbers are chosen: the pages around the current one are shown.

Create the file 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]]) ?>">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]]) ?>">Next</a>
        <?php endif; ?>
    </nav>
<?php endif; ?>

The variables pageCount, current, previous, next and pagesInRange are passed by the helper itself, and route is the fourth argument of paginationControl(). Open /blog: page numbers appear under the list of five posts.

Common mistake

Loading all rows with fetchAll() and splitting them with ArrayAdapter means reading the whole table from the database every time for the sake of five records. For database data use DbSelect: it requests only the rows of the current page.

Thanks for reading!

Donate to the site — we work hard for you.