germania-kg/pagination

Paginate your Traversables

1.0.4 2022-03-30 10:04 UTC

This package is auto-updated.

Last update: 2024-03-29 03:32:49 UTC


README

Packagist PHP version Build Status Scrutinizer Code Quality Code Coverage Build Status

Installation with Composer

$ composer require germania-kg/pagination

Overview

Pagination: Page numbering made easy! Supports current, next, previous, first and last page numbers as well as number of pages and customizable page sizes.

PaginationFactory: Callable class for creating a Pagination instance. Works great with $_GET['page']

PaginationIterator: Limits your Traversables according to the Pagination status. Useful for paginated JSON API resources.

JsonApiPaginationDecorator: Create useful links and meta information for your JSON API resource collection.

Usage

The Pagination class

Just pass the number of items to paginate. The Pagination class will calculate the page numbers, based on a default page size of 25. See below for customization examples.

<?hpp
use Germania\Pagination\Pagination;

$items_count = 100; // count( $things ) or integer;
$pagination = new Pagination( $items_count );

$pagination->getPagesCount(); // 4, with 25 items each
$pagination->getFirst(); // 0
$pagination->getLast(); // 3

Pagination status

Right after instantiation, no page was picked by the user. Hence, the Pagination is then considered inactive.

Please note that the current page also may be int(0) – the first page. This is why we have to check against null. The isActive method is a convenient alias for $p->getCurrent() === null .

$pagination->isActive();    // FALSE

$pagination->getCurrent();  // null
$pagination->getPrevious(); // null
$pagination->getNext();     // null

It first needs a setCurrent call to become active:

Setting the current page

use Germania\Pagination\PaginationRangeException;

try {
  $pagination->setCurrent( 1 );
  
  $pagination->isActive();    // TRUE
  $pagination->getCurrent();  // 1  
  $pagination->getPrevious(); // null
  $pagination->getNext();     // 2  
}
catch ( PaginationRangeException $e )
{
  echo $e->getMessage(); // "Invalid Page number"
  echo $e->getCode();    // 400
}

Setting the page size

Whilst the default number of items on a page is 25, you may set another size—up to 100 per default:

use Germania\Pagination\PaginationRangeException;

try {
	$pagination = new Pagination( $items_count );  
	$pagination->getPageSize(); // 25
	$pagination->setPageSize( 999 );  
}
catch ( PaginationRangeException $e ) 
{
  echo $e->getMessage(); // "Invalid Page size (max. 100)"
  echo $e->getCode();    // 400
}

Tweak the page sizes with constructor parameters:

$custom_page_size =  50; // default: 25
$pagination = new Pagination( $items_count, $custom_page_size );

$max_page_size    = 200; // default: 100
$pagination = new Pagination( $items_count, $custom_page_size, $max_page_size );

The PaginationFactory

The PaginationFactory constructor also accepts instances of Countable, Traversable or arrays, so you won't have to count the items yourself. The second parameter may be a page number integer or an array with number and/or size values.

<?php
use Germania\Pagination\PaginationFactory;

// Optinally set default page size
$factory = new PaginationFactory;
$factory = new PaginationFactory( 25 );

// Most simple: just integers
$items_count = 65;
$choose_page = 2;

// Create Pagination instance:
$pagination = $factory( $items_count, $choose_page );

Creation from array is useful when working with query parameters such as $_GET['page']

// Both elements are optional.
$pagination = $factory( $items_count, [
	'number' => 2, // default: 0
  'size'   => 20
]);

// User Input
$pagination = $factory( $items_count, $_GET['page'] ?? [] );

The PaginationIterator

Limits any \Traversable iterator to the current page size, depending on the pagination status. The PaginationIterator constructor accepts your iterator and your pagination instance. It is also \Countable to count the numbers of items shown on the current page.

<?php
use Germania\Pagination\PaginationIterator;

// Have your pagination at hand...
$pagination = ...
$pagination->setCurrent(2);

// Setup something really big
$collection = new MyHugeIterator( $thousand_items );

$paginated_collection = new PaginationIterator( $collection, $pagination );
echo count( $paginated_collection ); // 25

foreach( $paginated_collection as $item):
  // loop: 25 items on page 2
endforeach;

Which Iterator is used?

Depending on the pagination status, PaginationIterator's inner iterator used in the foreach loop is either \LimitIterator or the MyHugeIterator instance itself, when pagination is not active:

// this time, we do not pick a page number!
$thousand_items = ...
$pagination = new Pagination( count($thousand_items) );
$pagination->isActive(); // null

$collection = new MyHugeIterator( $thousand_items );

$paginated_collection = new PaginationIterator( $collection, $pagination );
$iterator = $paginated_collection->getIterator();

get_class( $iterator ); // MyHugeIterator instance

The JsonApiPaginationDecorator

This library provides a handy JsonApiPaginationDecorator which will generate useful information for your JSON API resource collection responses with the help of a given \Psr\Http\Message\UriInterface instance.

Meta information support

The meta member can be used to include non-standard meta-information, such as our pagination:

$pagination->setCurrent( 16 );
$pagination->setPageSize( 10 );

$links = $ja_decorator->getMeta();
// array(
//     numberOfPages => 23
//     currentPage   => 16
//     pageSize      => 10
// )

The result array will be empty, when the Pagination is inactive.

Links object support

The JSON API specs on fetching pagination proposes to use page[number] and page[size] for customizing the paged output. And it states the links object in a collection must use these key names, when working with pagination links:

  • first: the first page of data
  • last: the last page of data
  • prev: the previous page of data
  • next: the next page of data

The JsonApiPaginationDecorator generates this elements for you. The class is \JsonSerializable as well.

<?php
use Germania\Pagination\JsonApiPaginationDecorator;

// Prepare dependencies
$uri = \GuzzleHttp\Psr7\uri_for('http://example.com');
$pagination = ...
$pagination->setCurrent( 2 );

new $ja_decorator = new JsonApiPaginationDecorator( $pagination, $uri);

// These are equivalent:
$links = $ja_decorator->getLinks();
$links = $ja_decorator->jsonSerialize();

// array(
//     first    => http://example.com/?page[number]=0
//     last     => http://example.com/?page[number]=42
//     previous => http://example.com/?page[number]=1
//     next     => http://example.com/?page[number]=3
// )
Default Query Parameters

The JsonApiPaginationDecorator internally calls the withQuery method on the PSR-7 $uri instance. Unfortunately, this will replace any query parameters the URI contained. Just pass any needed query parameters as 3rd constructor parameter:

$params = array(
	'foo'  => 'bar',
  'json' => 'cool'
);
new $ja_decorator = new JsonApiPaginationDecorator( $pagination, $uri, $params);

$links = $ja_decorator->getLinks();
// array(
//     first    => http://example.com/?page[number]=0&foo=bar&json=cool
//     last     => http://example.com/?page[number]=42&foo=bar&json=cool
//     previous => http://example.com/?page[number]=1&foo=bar&json=cool
//     next     => http://example.com/?page[number]=3&foo=bar&json=cool
// )
Custom page sizes

In case you set a custom page size (which differs from the default size), the links will get an additional size field:

$pagination->setPageSize( 10 );

$links = $ja_decorator->getLinks();
// array(
//     first    => http://example.com/?page[number]=0&page[size]=10
//     last     => http://example.com/?page[number]=42&page[size]=10
//     previous => http://example.com/?page[number]=1&page[size]=10
//     next     => http://example.com/?page[number]=3&page[size]=10
// )
Edge cases

On the first and last page, links like previous and next do not make sense. Their value will be null:

$pagination = new Pagination (...);
new $ja_decorator = new JsonApiPaginationDecorator( $pagination, $uri);

$pagination->setCurrent( 0 );
$links = $ja_decorator->getLinks();
// array(
//     ...
//     previous => null
//     next     => http://example.com/?page[number]=1
// )

$pagination->setCurrent( 42 );
$links = $ja_decorator->getLinks();
// array(
//     ...
//     previous => http://example.com/?page[number]=41
//     next     => null
// )

When the pagination is not active, all values per default are null:

$pagination = new Pagination (...);
$pagination->isActive(); // FALSE

$ja_decorator->getLinks();
// array(
//     first    => null
//     last     => null
//     previous => null
//     next     => null
// )
Filtering the results

To get a clean, uncluttered links array, you may pass a boolean filter flag as fourth constructor parameter:

$filter = true;
new $ja_decorator = new JsonApiPaginationDecorator( $pagination, $uri, [], $filter);

$ja_decorator->getLinks();
// array()

Issues

See full issues list.

Development

$ git clone https://github.com/GermaniaKG/Pagination.git
$ cd Pagination
$ composer install

Unit tests

Either copy phpunit.xml.dist to phpunit.xml and adapt to your needs, or leave as is. Run PhpUnit test or composer scripts like this:

$ composer test
# or
$ vendor/bin/phpunit