Skip to content

Pagination & Filtering

Page through query results and apply global filters such as soft delete.

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();
$qb
->orderBy('created_at', 'DESC')
->orderBy('id', 'ASC');

Ordering matters for both user-facing list behavior and cursor stability.

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;
}
  • 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.