layout: default permalink: pagination/
When working with a large data set it obviously makes sense to offer pagination options to the endpoint, otherwise that data can get very slow. To avoid writing your own pagination output into every endpoint, Fractal provides you with two solutions:
Paginators offer more information about your result-set including total, and have next/previous links which will only show if there is more data available. This intelligence comes at the cost of having to count the number of entries in a database on each call.
For some data sets this might not be an issue, but for some it certainly will. If pure speed is an issue, consider using Cursors instead.
Paginator objects are created, and must implement League\Fractal\Pagination\PaginatorInterface
and its specified methods. The instantiated object must then be passed to the League\Fractal\Resource\Collection::setPaginator()
method.
Fractal currently ships with the following adapters:
illuminate/pagination
package as League\Fractal\Pagination\IlluminatePaginatorAdapter
pagerfanta/pagerfanta
package as League\Fractal\Pagination\PagerfantaPaginatorAdapter
zendframework/zend-paginator
package as League\Fractal\Pagination\ZendFrameworkPaginatorAdapter
As an example, you can use Laravel's Eloquent or Query Builder method paginate()
to achieve the following:
use League\Fractal\Resource\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
$paginator = Book::paginate();
$books = $paginator->getCollection();
$resource = new Collection($books, new BookTransformer);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
Below is an example of pagination using the Pagerfanter Paginator with a collection of objects obtained from Doctrine.
$doctrineAdapter = new DoctrineCollectionAdapter($allItems);
$paginator = new Pagerfanta($doctrineAdapter);
$filteredResults = $paginator->getCurrentPageResults();
$paginatorAdapter = new PagerfantaPaginatorAdapter($paginator, function(int $page) use (Request $request, RouterInterface $router) {
$route = $request->attributes->get('_route');
$inputParams = $request->attributes->get('_route_params');
$newParams = array_merge($inputParams, $request->query->all());
$newParams['page'] = $page;
return $router->generate($route, $newParams, 0);
});
$resource = new Collection($filteredResults, new BookTransformer);
$resource->setPaginator($paginatorAdapter);
In the example above, previous and next pages will be provided simply with ?page=#
ignoring all other
existing query strings. To include all query string values automatically in these links we can replace
the last line above with:
use Acme\Model\Book;
$year = Input::get('year');
$paginator = Book::where('year', '=', $year)->paginate(20);
$queryParams = array_diff_key($_GET, array_flip(['page']));
$paginator->appends($queryParams);
$paginatorAdapter = new IlluminatePaginatorAdapter($paginator);
$resource->setPaginator($paginatorAdapter);
When we have large sets of data and running a SELECT COUNT(*) FROM whatever
isn't really an option, we need a proper
way of fetching results. One of the approaches is to use cursors that will indicate to your backend where to start
fetching results. You can set a new cursor on your collections using the
League\Fractal\Resource\Collection::setCursor()
method.
The cursor must implement League\Fractal\Pagination\CursorInterface
and its specified methods.
Fractal currently ships with a very basic adapter: League\Fractal\Pagination\Cursor
. It's really easy to use:
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
use League\Fractal\Pagination\Cursor;
use League\Fractal\Resource\Collection;
$currentCursor = Input::get('cursor', null);
$previousCursor = Input::get('previous', null);
$limit = Input::get('limit', 10);
if ($currentCursor) {
$books = Book::where('id', '>', $currentCursor)->take($limit)->get();
} else {
$books = Book::take($limit)->get();
}
$newCursor = $books->last()->id;
$cursor = new Cursor($currentCursor, $previousCursor, $newCursor, $books->count());
$resource = new Collection($books, new BookTransformer);
$resource->setCursor($cursor);
These examples are for Laravel's illuminate\database
package, but you can do it however you like. The cursor
also happens to be constructed from the id
field, but it could just as easily be an offset number. Whatever
is picked to represent a cursor, maybe consider using base64_encode()
and base64_decode()
on the values to make sure
API users do not try and do anything too clever with them. They just need to pass the cursor to the new URL, not do any maths.
GET /books?cursor=5&limit=5
{
"books": [
{ "id": 6 },
{ "id": 7 },
{ "id": 8 },
{ "id": 9 },
{ "id": 10 }
],
"meta": {
"cursor": {
"previous": null,
"current": 5,
"next": 10,
"count": 5
}
}
}
On the next request, we move the cursor forward.
cursor
to next
from the last responseprevious
to current
from the last responselimit
is optional
count
from the previous request to maintain the same limitGET /books?cursor=10&previous=5&limit=5
{
"books": [
{ "id": 11 },
{ "id": 12 },
{ "id": 13 },
{ "id": 14 },
{ "id": 15 }
],
"meta": {
"cursor": {
"previous": 5,
"current": 10,
"next": 15,
"count": 5
}
}
}