Pagination
Lacelium includes an intelligent query paginator designed to work natively with Eloquent and Twig, automatically maintaining query string parameters, sort orders, and search filters across page transitions.
Controller Integration
The Lacelium\Core\Pagination\Paginator class wraps any Eloquent or Capsule query builder instance. Always clone your builder instance when executing the final slice query so the total count calculation remains isolated.
namespace App\Http\Controllers;
use App\Models\Task;
use Lacelium\Core\Http\Request;
use Lacelium\Core\Http\Response;
use Lacelium\Core\Pagination\Paginator;
class TaskController
{
public function index(Request $request, Response $response): Response
{
// 1. Build filtered base query
$query = Task::query()
->when($request->query('status'), fn($q, $s) => $q->where('status', $s))
->orderByDesc('created_at');
// 2. Instantiate paginator (computes total count & current page automatically)
$perPage = 15;
$paginator = Paginator::fromQuery($query, $request, perPage: $perPage);
// 3. Slice and fetch the active page models
$items = (clone $query)
->forPage($paginator->currentPage, $perPage)
->get();
return $response->render('docs/tasks/index.twig', [
'items' => $items,
'paginator' => $paginator,
]);
}
}
Rendering in Twig Templates
Include the framework's pre-styled docs/paginator.twig component at the bottom of your data tables or lists. It automatically generates pagination controls with accessible ARIA tags and active state indicators.
{# Loop through paginated items #}
{% for task in items %}
{{ task.title }}
{{ task.status }}
{% else %}
No records found.
{% endfor %}
{# Render standard navigation buttons & metadata #}
{% include 'docs/paginator.twig' with { paginator: paginator } %}
Live Interactive Preview
Experience how Lacelium's pagination buttons preserve existing query parameters (such as search keywords and sorting fields) while updating the page index:
Paginator Properties & Methods
The Paginator object exposes metadata used for custom UI widgets or JSON API responses:
| Property / Method | Return Type | Description |
|---|---|---|
$paginator->currentPage |
int |
Current page number (1-indexed). |
$paginator->total |
int |
Total count of matching records across the entire dataset. |
$paginator->perPage |
int |
Maximum number of items displayed per page. |
$paginator->lastPage |
int |
Total number of pages calculated as ceil($total / $perPage). |
$paginator->hasPages() |
bool |
Returns true if total records exceed perPage. |
$paginator->hasMorePages() |
bool |
Returns true if current page is less than lastPage. |
$paginator->url(int $page) |
string |
Builds query string preserving all existing query parameters while updating page. |
SPA Zero-Refresh Updates
When combined with Lacelium's SPA routing layer (data-spa="true"), clicking pagination links seamlessly replaces table content and URL history without a full page reload:
<!-- Enable zero-reload SPA navigation on data container -->
<div id="tasks-table-container" data-spa-fragment="true">
</div>
Performance Guidelines
- Large Datasets (>100,000 Rows): On massive tables,
COUNT(*)can be costly. For analytical dashboards, consider cursor-based pagination withwhere('id', '>', $lastId). - Eager Loading Relations: Always combine with
with(['relationship'])on the sliced query to avoid N+1 query execution. - Sanitize Bounds: The paginator automatically guards against negative page indices and numbers exceeding
lastPage.
Next Steps
- Build responsive data grids with multi-column sorting in DataTable Pattern.
- Display item detail views without reloading using Modals Architecture.
- Query models with fluent conditions and joins in Database & Eloquent.