Pagination controls how many records are returned and how they're split into pages.
The PagePaginator
is the most common pagination specification that provides simple page-based navigation:
use Spiral\DataGrid\Specification\Pagination\PagePaginator;
// Basic pagination with 10 items per page
$schema->setPaginator(new PagePaginator(10));
// Pagination with allowed page sizes
$schema->setPaginator(new PagePaginator(25, [10, 25, 50, 100]));
The PagePaginator
accepts user input with two keys:
limit
- Number of items per page (must be in allowedLimits array)page
- Current page number (1-based)$paginator = new PagePaginator(10, [25, 50, 100, 500]);
$paginator->withValue(['limit' => 123]); // Won't apply (not in allowed limits)
$paginator->withValue(['limit' => 50]); // Will apply
$paginator->withValue(['limit' => 100]); // Will apply
$paginator->withValue(['limit' => 100, 'page' => 2]); // Page 2 with 100 items
GET /api/products?paginate[page]=2&paginate[limit]=50
Under the hood, the PagePaginator
converts limit
and page
into Limit
and Offset
specifications:
// User input: page=3, limit=25
// Converts to:
// - Limit: 25
// - Offset: 50 (calculated as (page - 1) * limit)
When you use pagination, the grid provides metadata about the current page state:
public function products(ProductSchema $schema, GridFactoryInterface $factory, ProductRepository $products)
{
$grid = $factory->create($products->select(), $schema);
return [
'products' => iterator_to_array($grid),
'pagination' => $grid->getOption(GridInterface::PAGINATOR),
'total' => $grid->getOption(GridInterface::COUNT),
];
}
The pagination metadata includes:
[
'page' => 2, // Current page number
'limit' => 25, // Items per page
'count' => 25, // Items on current page
'countPages' => 8, // Total number of pages
'countTotal' => 200 // Total number of items
]
class ProductSchema extends GridSchema
{
public function __construct()
{
// Different page sizes for different use cases
$this->setPaginator(new PagePaginator(
24, // Default: 24 products (4x6 grid)
[12, 24, 48, 96] // Options: Small, medium, large, extra large
));
}
}
class UserManagementSchema extends GridSchema
{
public function __construct()
{
// Higher default for admin interfaces
$this->setPaginator(new PagePaginator(
50, // Default: 50 users
[25, 50, 100, 200, 500] // Wide range for bulk operations
));
}
}
class MobileProductSchema extends GridSchema
{
public function __construct()
{
// Smaller pages for mobile to improve loading times
$this->setPaginator(new PagePaginator(
10, // Default: 10 items
[5, 10, 20] // Limited options for mobile
));
}
}
You can create custom pagination logic by implementing your own paginator:
use Spiral\DataGrid\SpecificationInterface;
class CursorPaginator implements SpecificationInterface
{
public function __construct(
private readonly int $limit = 25,
private readonly ?string $cursor = null
) {}
public function withValue(mixed $value): ?SpecificationInterface
{
if (!is_array($value)) {
return null;
}
return new self(
$value['limit'] ?? $this->limit,
$value['cursor'] ?? $this->cursor
);
}
// Implementation methods...
}
// Usage
$schema->setPaginator(new CursorPaginator(20));
class InfiniteScrollPaginator implements SpecificationInterface
{
public function __construct(
private readonly int $limit = 20,
private readonly ?int $lastId = null
) {}
public function withValue(mixed $value): ?SpecificationInterface
{
if (!is_array($value)) {
return null;
}
return new self(
$value['limit'] ?? $this->limit,
$value['last_id'] ?? $this->lastId
);
}
// Implementation methods...
}
// Usage
$schema->setPaginator(new InfiniteScrollPaginator(15));
For performance reasons, you might want to control how total counts are calculated:
/** @var Spiral\DataGrid\GridFactory $factory */
$factory = $factory->withCounter(static function ($select): int {
// Custom counting logic
return count($select) * 2;
});
// For very large datasets, you might want to disable total counting
$factory = $factory->withCounter(static function ($select): int {
return -1; // Indicates unknown total
});