Skip to content

Pagination ​

Pagination is supported via the paginate() method on the Query Builder, with server cursors or offsets depending on the dialect. limit() sets the page size.

php
$page = $repository->createQueryBuilder()
    ->paginate()
    ->limit(10)
    ->getQuery()
    ->getResult(); // returns PaginatedResult<Task>

// Navigate pages
$page->items;           // Task[]
$page->hasNextPage;     // bool
$page->hasPreviousPage; // bool
$page->endCursor;       // string|null

$nextPage = $page->next();     // PaginatedResult<Task>|null
$prevPage = $page->previous(); // PaginatedResult<Task>|null

hasPreviousPage tells the page was reached from another one, through next() or a starting cursor: previous() goes back through the cursors met. next() returns null on the last page, previous() on the first one.

To start from a specific cursor:

php
$page = $repository->createQueryBuilder()
    ->paginate(after: 'cursor-string')
    ->limit(10)
    ->getQuery()
    ->getResult();

Going through every page ​

toIterable() returns a generator going through every page, one query per page, the next page being fetched once the current one is read. A command or an export processes a large result set with the memory of a single page, as long as it does not keep the entities it read:

php
use GraphqlOrm\Query\Direction;

$tasks = $taskRepository->createQueryBuilder()
    ->orderBy('id', Direction::Asc)
    ->limit(500) // the page size
    ->paginate()
    ->getQuery()
    ->toIterable();

foreach ($tasks as $task) {
    $exporter->write($task);
}

The query must be paginated, otherwise toIterable() throws a LogicException. Each page has its own identity map: an entity met on two pages is hydrated twice, and the pages are kept out of the shared identity map. Order the query on a unique field, so that the pages do not overlap.

Dialects ​

DialectPagination
DataApiBuilderDialectserver cursors: first, after, hasNextPage, endCursor
RelayDialectserver cursors: first, after, pageInfo { hasNextPage endCursor }
PostGraphileDialectserver cursors: first, after, pageInfo { hasNextPage endCursor }
HasuraDialectoffsets: limit, offset; limit() is required (LogicException)
DefaultDialectnone: getQuery() throws a LogicException on a paginated query

With an offset pagination, the cursor of a page is the offset of the next one ("10", "20"...), and one row more than the page size is requested to tell whether a next page exists, so that hasNextPage is exact. A cursor that is not an offset throws an InvalidArgumentException. The navigation is the same as with server cursors. See dialects.md to write a dialect implementing CursorPaginationDialect or OffsetPaginationDialect.

Released under the MIT License.