Paginating data collections with zend-paginator

by Enrico Zimuel

zend-paginator1 is a flexible component for paginating collections of data and presenting that data to users.

Pagination2 is a standard UI solution to manage the visualization of lists of items, like a list of posts in a blog or a list of products in an online store.

zend-paginator is very popular among Zend Framework developers, and it's often used with zend-view3, thanks to the pagination control view helper zend-view provides.

It can be used also with other template engines. In this article, I will demonstrate how to use it with Plates4.

Usage of zend-paginator

The component can be installed via Composer:

$ composer require zendframework/zend-paginator

To consume the paginator component, we need a collection of items. zend-paginator ships with several different adapters for common collection types:

  • ArrayAdapter, which works with PHP arrays;
  • Callback, which allows providing callbacks for obtaining counts of items and lists of items;
  • DbSelect, to work with a SQL collection (using zend-db5);
  • DbTableGateway, to work with a Table Data Gateway (using the TableGateway feature from zend-db.
  • Iterator, to work with any Iterator6 instance.

If your collection does not fit one of these adapters, you can create a custom adapter. To do so, you will need to implement Zend\Paginator\Adapter\AdapterInterface, which defines two methods:

  • count() : int
  • getItems(int $offset, int $itemCountPerPage) : array

Each adapter needs to return the total number of items in the collection, implementing the count() method, and a portion (a page) of items starting from $offset position with a size of $itemCountPerPage per page.

With these two methods, we can use zend-paginator with any type of collection.

For instance, imagine we need to paginate a collection of blog posts and we have a Posts class that manages all the posts. We can implement an adapter like this:

require 'vendor/autoload.php';

use Zend\Paginator\Adapter\AdapterInterface;
use Zend\Paginator\Paginator;
use Zend\Paginator\ScrollingStyle\Sliding;

class Posts implements AdapterInterface
{
    private $posts = [];

    public function __construct()
    {
      // Read posts from file/database/whatever
    }

    public function count()
    {
        return count($this->posts);
    }

    public function getItems($offset, $itemCountPerPage)
    {
        return array_slice($this->posts, $offset, $itemCountPerPage);
    }
}

$posts = new Posts();
$paginator = new Paginator($posts);

Paginator::setDefaultScrollingStyle(new Sliding());
$paginator->setCurrentPageNumber(1);
$paginator->setDefaultItemCountPerPage(8);

foreach ($paginator as $post) {
  // Iterate on each post
}

$pages = $paginator->getPages();
var_dump($pages);

In this example, we created a zend-paginator adapter using a custom Posts class. This class stores the collection of posts using a private array ($posts). This adapter is then passed to an instance of Paginator.

When creating a Paginator, we need to configure its behavior. The first setting is the scrolling style. In the example above, we used the Sliding7 style, a Yahoo!-like scrolling style that positions the current page number as close as possible to the center of the page range.

Scrolling style

Note: the Sliding scrolling style is the default style used by zend-paginator. We need to set it explicitly using Paginator::setDefaultScrollingStyle() only if we do not use zend-servicemanager8 as a plugin manager. Otherwise, the scrolling style is loaded by default from the plugin manager.

The other two configuration values are the current page number and the number of items per page. In the example above, we started from page 1, and we count 8 items per page.

We can then iterate on the $paginator object to retrieve the post of the current page in the collection.

At the end, we can retrieve the information regarding the previous page, the next page, the total items in the collection, and more. To get these values we need to call the getPages() method. We will obtain an object like this:

object(stdClass)#81 (13) {
  ["pageCount"]=>
  int(3)
  ["itemCountPerPage"]=>
  int(8)
  ["first"]=>
  int(1)
  ["current"]=>
  int(1)
  ["last"]=>
  int(3)
  ["next"]=>
  int(2)
  ["pagesInRange"]=>
  array(3) {
    [1]=>
    int(1)
    [2]=>
    int(2)
    [3]=>
    int(3)
  }
  ["firstPageInRange"]=>
  int(1)
  ["lastPageInRange"]=>
  int(3)
  ["currentItemCount"]=>
  int(8)
  ["totalItemCount"]=>
  int(19)
  ["firstItemNumber"]=>
  int(1)
  ["lastItemNumber"]=>
  int(8)
}

Using this information, we can easily build an HTML footer to navigate across the collection.

Note: using zend-view, we can consume the paginationControl()9 helper, which emits an HTML pagination bar.

An example using Plates

Plates10 implements templates using native PHP; it is fast and easy to use, without any additional meta language; it is just PHP.

In our example, we will create a Plates template to paginate a collection of data using zend-paginator. We will use Bootstrap11 as the UI framework.

For purposes of this example, blog posts will be accessible via the following URL:

/blog[/page/{page:\d+}]

where [/page/{page:\d+}] represents the optional page number (using the regexp \d+ to validate only digits). If we open the /blog URL we will get the first page of the collection. To return the second page we need to connect to /blog/page/2, third page to /blog/page/3, and so on.

For instance, we can manage the page parameter using a PSR-7 middleware class consuming the previous Posts adapter, that works as follow:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use League\Plates\Engine;
use Zend\Paginator\Paginator;
use Zend\Paginator\ScrollingStyle\Sliding;
use Posts;

class PaginatorMiddleware
{
    /** @var Posts */
    protected $posts;

    /** @var Engine */
    protected $template;

    public function __construct(Posts $post, Engine $template = null)
    {
        $this->posts    = $post;
        $this->template = $template;
    }

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response, callable $next = null
    ) {
        $paginator = new Paginator($this->posts);
        $page = $request->getAttribute('page', 1);

        Paginator::setDefaultScrollingStyle(new Sliding());
        $paginator->setCurrentPageNumber($page);
        $paginator->setDefaultItemCountPerPage(8);

        $pages = $paginator->getPages();
        $response->getBody()->write(
            $this->template->render('posts', [
                'paginator' => $paginator,
                'pages'     => $pages,
            ])
        );
        return $response;
    }
}

We used a posts.php template, passing the paginator ($paginator) and the pages ($pages) instances. That template could then look like the following:

<?php $this->layout('template', ['title' => 'Blog Posts']) ?>

<div class="container">
  <h1>Blog Posts</h1>

  <?php foreach ($paginator as $post) : ?>
    <div class="row">
      <?php // prints the post title, date, author, ... ?>
    </div>
  <?php endforeach ?>

  <?php $this->insert('page-navigation', ['pages' => $pages]) ?>
</div>

The page-navigation.php template contains the HTML code for the page navigation control, with button like previous, next, and page numbers.

<nav aria-label="Page navigation">
  <ul class="pagination">
    <?php if (! isset($pages->previous)) : ?>
      <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>
    <?php else : ?>
      <li><a href="/blog/page/<?= $pages->previous ?>" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>
    <?php endif ?>

    <?php foreach ($pages->pagesInRange as $num) : ?>
      <?php if ($num === $pages->current) : ?>
        <li class="active"><a href="/blog/page/<?= $num ?>"><?= $num ?> <span class="sr-only">(current)</span></a></li>
      <?php else : ?>
        <li><a href="/blog/page/<?= $num ?>"><?= $num ?></a></li>
      <?php endif ?>
    <?php endforeach ?>

    <?php if (! isset($pages->next)) : ?>
      <li class="disabled"><a href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>
    <?php else : ?>
      <li><a href="/blog/page/<?= $pages->next ?>" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>
    <?php endif ?>
  </ul>
</nav>

Summary

The zend-paginator component of Zend Framework is a powerful and easy to use package that provides pagination of data. It can be used as standalone component in many PHP projects using different frameworks and template engines. In this article, I demonstrated how to use it in general purpose applications. Moreover, I showed an example using Plates and Bootstrap, in a PSR-7 middleware scenario.

Visit the zend-paginator documentation12 to find out what else you might be able to do with this component!

Footnotes

1. https://docs.zendframework.com/zend-paginator/
2. https://en.wikipedia.org/wiki/Pagination
3. https://docs.zendframework.co/zend-view/
4. http://platesphp.com/
5. https://docs.zendframework.com/zend-db/
6. http://php.net/iterator
7. https://github.com/zendframework/zend-paginator/blob/master/src/ScrollingStyle/Sliding.php
8. https://docs.zendframework.com/zend-servicemanager/
9. https://docs.zendframework.com/zend-paginator/usage/#rendering-pages-with-view-scripts
10. http://platesphp.com/
11. http://getbootstrap.com/
12. https://docs.zendframework.com/zend-paginator/

results matching ""

    No results matching ""