← Persistence

PERSISTENCE FEATURE

Pagination

Offset and cursor pagination out of the box — switch modes with a single query parameter.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Offset and cursor pagination out of the box — switch modes with a single query parameter.

Offset pagination

Current page window

Page 1 of 7 with 62 total products and 10 rows per page.

1 Current page
7 Total pages
62 Total rows
Name Price
Portable Monitor $249.99
Refactoring $49.99
Power Bank $59.99
Domain-Driven Design $54.99
Head First Design Patterns $49.99
Linen Shirt $54.99
Winter Jacket $149.99
Ergonomic Chair $299.99
Smart Thermostat $199.99
Foam Roller $29.99
← Prev Next →
PaginatedResult limit() offset() cursor pagination total count

Verified against Semitexa Ultimate 2026.09.19.1020

Pagination

Semitexa ORM repositories support both offset-based and cursor-based pagination through the same query API, switchable at runtime via a request parameter.

How it works

A handler reads mode, limit, and page from the payload, validates bounds (page >= 1, limit > 0 with an upper cap), computes offset = (page - 1) * limit, and calls findPage($limit, $offset) on the repository. The repository uses limit() and offset() on the query builder. Switching to cursor mode changes only the query strategy — the handler and repository interface stay identical. A countAll() call provides the total for page count computation, and the validated bounds keep offsets non-negative.

Why this matters

Offset pagination is simple but degrades on large datasets. Cursor pagination stays performant at scale. Having both available through the same repository contract means the choice can be deferred until the data size makes it matter, without rewriting handler logic.

© Linus Torvalds: "Talk is cheap. Show me the code."

Handler Application entry point
<?phpdeclare(strict_types=1);namespace App\Application\Handler\Data;use App\Application\Payload\Data\ProductCatalogPayload;use App\Application\Resource\Page\ProductCatalogResource;use App\Domain\Catalog\ProductReadRepositoryInterface;use Semitexa\Core\Attribute\AsPayloadHandler;use Semitexa\Core\Attribute\InjectAsReadonly;use Semitexa\Core\Contract\TypedHandlerInterface;#[AsPayloadHandler(payload: ProductCatalogPayload::class, resource: ProductCatalogResource::class)]final class ProductCatalogHandler implements TypedHandlerInterface{    #[InjectAsReadonly]    protected ProductReadRepositoryInterface $products;    public function handle(ProductCatalogPayload $payload, ProductCatalogResource $resource): ProductCatalogResource    {        return $resource->fromProducts($this->products->findForCatalog($payload));    }}

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal