PaginationHelper slices any array or collection into pages and produces the surrounding page numbers needed to render a pagination UI. It is designed for HTML views — pair it with the built-in pagination component to get a fully working page navigator.
For API endpoints use Cursor Pagination instead.
use PHP_SF\System\Classes\Helpers\PaginationHelper;
$page = (int) $this->request->query->get( 'page', 1 );
$players = User::findAll(); // array or collection
$ph = new PaginationHelper(
arr: $players,
messagesPerPage: 20,
page: $page,
);
return $this->render( players_page::class, [
'players' => $ph->paginate(), // items for the current page
'pages' => $ph->getPages(), // page numbers for the nav component
] );
new PaginationHelper(
iterable $arr,
int $messagesPerPage = 10,
int $page = 1,
)
| Parameter | Default | Description |
|---|---|---|
$arr |
— | Any array or object with a count() method |
$messagesPerPage |
10 |
Items shown per page |
$page |
1 |
Current page number (1-based) |
If $page exceeds the total number of pages, it is clamped to the last page automatically.
public function paginate(): array
Returns only the items belonging to the current page, preserving the original array keys.
$ph = new PaginationHelper( $items, 10, $page );
$current = $ph->paginate(); // items for page $page
public function getTotalPages(): int
Total number of pages. Useful for displaying "Page 3 of 12" in a view.
$total = $ph->getTotalPages();
public function getPages(): array
Returns an associative array of page numbers surrounding the current page — at most two to the left, the current, and two to the right, plus first and last if the current page is far enough from either end:
[
'first' => 1, // present if current > 3
'page2left' => 3,
'page1left' => 4,
'current' => 5,
'page1right' => 6,
'page2right' => 7,
'last' => 20, // present if current + 2 < total
]
Keys are omitted when they would fall outside the valid range. On a single-page result, the array is empty.
PHP_SF\Templates\Components\pagination is a built-in view component that turns the $pages array from getPages() into a Bootstrap-compatible <ul> navigator. It lives at Platform/templates/Components/pagination.php.
| Prop | Type | Description |
|---|---|---|
pages |
array |
Output of PaginationHelper::getPages() |
onclickFunction |
string |
Name of a JS function to call with the page number when a button is clicked |
// In the view (AbstractView::show()):
$this->import( pagination::class, [
'pages' => $this->pages,
'onclickFunction' => 'changePage',
] );
The component renders nothing when $pages is empty (single page or no items) — no need to guard the import call.
The component calls onclickFunction(pageNumber) on each clickable button. Define the function in your page JS before the component renders:
function changePage(page) {
window.location.href = updateQueryParam( window.location.href, 'page', page );
}
For AJAX-powered tables that reload without a full page navigation:
function changePage(page) {
loadPlayersPage( page );
}
The component renders a <ul class="page-numbers pagination pagination-sm"> with <li> elements. Each button carries one of two CSS classes:
| Class | Meaning |
|---|---|
pagination-link enabled |
Clickable — has an onclick handler |
pagination-link disabled |
Not clickable — used for the current page and ... ellipsis gaps |
The current page <li> additionally gets the current class.
Example output for page 5 of 20:
<ul class="page-numbers pagination pagination-sm">
<li class="pagination-link enabled" onclick="changePage(1)"><span>1</span></li>
<li class="pagination-link disabled"><span>...</span></li>
<li class="pagination-link enabled" onclick="changePage(3)"><span>3</span></li>
<li class="pagination-link enabled" onclick="changePage(4)"><span>4</span></li>
<li class="pagination-link disabled current"><span>5</span></li>
<li class="pagination-link enabled" onclick="changePage(6)"><span>6</span></li>
<li class="pagination-link enabled" onclick="changePage(7)"><span>7</span></li>
<li class="pagination-link disabled"><span>...</span></li>
<li class="pagination-link enabled" onclick="changePage(20)"><span>20</span></li>
</ul>
The component also emits a small inline <script> that applies border-radius to the first and last visible buttons — rounding the ends of the pill shape without requiring extra CSS.
The component uses Bootstrap's pagination and pagination-sm classes. Add your own rules targeting .pagination-link to override appearance:
.pagination-link.enabled:hover { background: #2a2a3e; }
.pagination-link.current { font-weight: bold; color: #fff; }
Controller:
$ph = new PaginationHelper( $players, messagesPerPage: 25, page: $page );
return $this->render( players_page::class, [
'players' => $ph->paginate(),
'pages' => $ph->getPages(),
] );
View:
// render the list
foreach ( $this->players as $player ) { ... }
// render the navigator
$this->import( pagination::class, [
'pages' => $this->pages,
'onclickFunction' => 'changePage',
] );
Page JS:
function changePage(page) {
window.location.href = updateQueryParam( window.location.href, 'page', page );
}
#[Route( url: 'players', httpMethod: 'GET', middleware: auth::class )]
public function players_list(): Response
{
$page = max( 1, (int) $this->request->query->get( 'page', 1 ) );
$players = User::findAll();
$ph = new PaginationHelper( $players, messagesPerPage: 25, page: $page );
return $this->render( players_page::class, [
'players' => $ph->paginate(),
'pages' => $ph->getPages(),
'total_pages' => $ph->getTotalPages(),
'current_page'=> $page,
] );
}
PaginationHelper loads the entire dataset into memory before slicing it. For large tables — thousands of rows or more — load only the page you need from the database:
// Efficient: fetch only the current page from Doctrine
$players = em( 'postgresql' )
->getRepository( Player::class )
->findBy( [], ['createdAt' => 'DESC'], limit: 25, offset: ($page - 1) * 25 );
Use the same PaginationHelper for getPages() with a lightweight count-only query:
$totalCount = (int) em( 'postgresql' )->createQueryBuilder()
->select( 'COUNT(e.id)' )
->from( Player::class, 'e' )
->getQuery()
->getSingleScalarResult();
// Feed a dummy range of the right size for page number generation only
$ph = new PaginationHelper( range( 1, $totalCount ), 25, $page );
$pages = $ph->getPages();
For API endpoints where clients need to page through large datasets without knowing total counts, use Cursor Pagination instead.