Pagination & Filtering
Page through query results and apply global filters such as soft delete.
Pagination strategies
Section titled “Pagination strategies”Uses the last seen ordered value instead of a numeric offset:
$qb ->orderBy('id', 'ASC') ->cursorLimit(10);
$result = $qb->getCursorPaginatedResult();
$items = $result->getItems();$cursor = $result->getNextCursor(); // opaque token encoding the last row's order-column value(s) — pass it to cursor() to fetch the next page// Next page:$qb ->orderBy('id', 'ASC') ->cursor($cursor) ->cursorLimit(10);
$result = $qb->getCursorPaginatedResult();Simple and useful for small or administrative lists:
$qb->limit(10)->offset(20);It can become expensive or unstable on large, frequently changing datasets.
Ordering
Section titled “Ordering”$qb ->orderBy('created_at', 'DESC') ->orderBy('id', 'ASC');Ordering matters for both user-facing list behavior and cursor stability.
Soft delete filter
Section titled “Soft delete filter”Register SoftDeleteFilter to automatically exclude records with a deleted marker such as deleted_at. The filter is not enabled automatically — register it on each EntityManager that should apply it:
use Articulate\Modules\QueryBuilder\Filter\SoftDeleteFilter;
$entityManager->getFilters()->add('soft_delete', new SoftDeleteFilter());Once registered, every query built through that EntityManager excludes soft-deleted rows by default. Use withoutFilter('soft_delete') on a single query builder when an administrative query intentionally needs to include soft-deleted records:
$entityManager ->createQueryBuilder(Customer::class) ->withoutFilter('soft_delete') ->getResult();Mark the entity itself with #[SoftDeleteable] so remove() sets the marker instead of issuing a DELETE:
#[SoftDeleteable(fieldName: 'deleted_at', columnName: 'deleted_at')]class Customer{ #[Property(name: 'deleted_at', nullable: true)] public ?string $deleted_at = null;}Common pitfalls
Section titled “Common pitfalls”- Soft-deleted rows are hidden from primary-key
find()calls as well as list queries. withoutFilter('soft_delete')applies to the query builder where it is called — it does not turn the filter off globally.- Cursor pagination needs a unique or tie-broken order, such as timestamp plus
id.