Pagination
The TYPO3 Core provides an interface to implement the native pagination of lists like arrays or query results of Extbase.
The foundation of that new interface
\TYPO3\ is that
it's type agnostic. It means, that it doesn't define the type of paginatable objects. It's up to the
concrete implementations to enable pagination for specific types. The interface only forces you to
reduce the incoming list of items to an
iterable sub set of items.
Along with that interface, an abstract paginator class
\TYPO3\
is available that implements the base pagination logic for any kind of
Countable set of
items while it leaves the processing of items to the concrete paginator class.
Table of Contents
Paginators
New in version 14.2
The
Query has been
introduced.
Three concrete paginators are available:
- For type
array:\TYPO3\CMS\ Core\ Pagination\ Array Paginator - For type
\TYPO3\:CMS\ Extbase\ Persistence\ Query Result Interface \TYPO3\CMS\ Extbase\ Pagination\ Query Result Paginator - For type
\TYPO3\:CMS\ Core\ Database\ Query\ Query Builder \TYPO3\CMS\ Core\ Pagination\ Query Builder Paginator
Example: ArrayPaginator
Code example for the
Array in an
Extbase controller:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
final class ExampleController extends ActionController
{
public function myAction(): ResponseInterface
{
// For better demonstration we create fixed items, in real
// world usage a list of models is used instead.
$itemsToBePaginated = ['apple', 'banana', 'strawberry', 'raspberry', 'pineapple'];
$itemsPerPage = 2;
$currentPageNumber = 3;
$paginator = new ArrayPaginator($itemsToBePaginated, $currentPageNumber, $itemsPerPage);
$paginator->getNumberOfPages(); // returns 3
$paginator->getCurrentPageNumber(); // returns 3, basically just returns the input value
$paginator->getKeyOfFirstPaginatedItem(); // returns 4
$paginator->getKeyOfLastPaginatedItem(); // returns 4
$pagination = new SimplePagination($paginator);
$pagination->getAllPageNumbers(); // returns [1, 2, 3]
$pagination->getPreviousPageNumber(); // returns 2
$pagination->getNextPageNumber(); // returns null
// ... more logic ...
$this->view->assignMultiple(
[
'pagination' => $pagination,
'paginator' => $paginator,
],
);
return $this->htmlResponse();
}
}
And the corresponding Fluid template:
<ul class="pagination">
<f:for each="{pagination.allPageNumbers}" as="page">
<li class="page-item">
<f:link.action
arguments="{currentPageNumber:page}"
class="page-link {f:if(condition:'{currentPageNumber}=={page}',then:'active')}"
>
{page}
</f:link.action>
</li>
</f:for>
</ul>
<f:for each="{paginator.paginatedItems}" as="item">
{item}
</f:for>
Example: QueryBuilderPaginator
The paginated items are fetched only once per page request by storing the result internally, avoiding double execution of the database statement.
The total item count is determined robustly using a common table expression
(CTE) wrapping the passed QueryBuilder instance.
This approach correctly handles advanced queries involving
UNION, nested
CTEs, windowing functions, or grouping.
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Pagination\QueryBuilderPaginator;
use TYPO3\CMS\Core\Pagination\SimplePagination;
$paginator = new QueryBuilderPaginator(
queryBuilder: $queryBuilder,
currentPageNumber: $currentPage,
itemsPerPage: 10,
);
$pagination = new SimplePagination($paginator);
// Retrieve the items for the current page
$items = $paginator->getPaginatedItems();
Note
The
Query does
not handle language overlays. Applying overlays on the result set can
lead to unexpected item count differences between pages when some records
are hidden after overlay processing. Use
Query or
Array when language
overlay handling is required.
The paginator also takes full control over LIMIT and OFFSET
and does not respect any existing limit/offset constraints on the passed
Query instance.
Sliding window pagination
The sliding window pagination can be used to paginate array items or query results from Extbase. The main advantage is that it reduces the amount of pages shown.
Example: Imagine 1000 records and 20 items per page which would lead to
50 links. Using the Sliding, you will get something like
this < prev ... 21 22 23 24 ... next > or < 1 ... 21 22 23 24 ... 50 > or
simple < 21 22 23 24 >. Customise the template to suit your needs.
Usage
Replace the usage of
Simple with
\TYPO3\ and you are done. Set
the 2nd argument to the maximum number of links which should be rendered.
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Domain\Repository\ExampleRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Pagination\SlidingWindowPagination;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
final class ExampleController extends ActionController
{
public function __construct(
private readonly ExampleRepository $exampleRepository,
) {}
public function myAction(): ResponseInterface
{
$allItems = $this->exampleRepository->findAll();
$currentPage = $this->request->hasArgument('currentPageNumber')
? (int)$this->request->getArgument('currentPageNumber')
: 1;
$itemsPerPage = 10;
$maximumLinks = 15;
$paginator = new QueryResultPaginator(
$allItems,
$currentPage,
$itemsPerPage,
);
$pagination = new SlidingWindowPagination(
$paginator,
$maximumLinks,
);
// ... more logic ...
$this->view->assignMultiple([
'pagination' => $pagination,
'paginator' => $paginator,
]);
return $this->htmlResponse();
}
}