PHPackages                             wiistriker/doctrine-cursor-paginator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. wiistriker/doctrine-cursor-paginator

ActiveLibrary[Database &amp; ORM](/categories/database)

wiistriker/doctrine-cursor-paginator
====================================

Iterate through large datasets

1.0.1(2w ago)226MITPHPPHP &gt;=8.1

Since Apr 27Pushed 2w agoCompare

[ Source](https://github.com/wiistriker/doctrine-cursor-paginator)[ Packagist](https://packagist.org/packages/wiistriker/doctrine-cursor-paginator)[ RSS](/packages/wiistriker-doctrine-cursor-paginator/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (12)Versions (3)Used By (0)

Doctrine ORM and DBAL Cursor Paginator for large datasets
=========================================================

[](#doctrine-orm-and-dbal-cursor-paginator-for-large-datasets)

[![Latest Version on Packagist](https://camo.githubusercontent.com/cbfec670ee43a51be9dba4495aafbc5821efde58805d35d78ba9aed2f75c024f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f776969737472696b65722f646f637472696e652d637572736f722d706167696e61746f722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/wiistriker/doctrine-cursor-paginator)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![Total Downloads](https://camo.githubusercontent.com/d643c4f8f346e01ea30e68c232641834b22dda9bea01f63d5cf5f81aeb934dc1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f776969737472696b65722f646f637472696e652d637572736f722d706167696e61746f722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/wiistriker/doctrine-cursor-paginator)

Iterate through large database results with easy

Installation
------------

[](#installation)

```
composer require wiistriker/doctrine-cursor-paginator

```

Usage
-----

[](#usage)

Both Doctrine ORM and DBAL query builders are supported:

- [Usage for ORM](#usage-for-orm)
- [Usage for DBAL](#usage-for-dbal)

Usage for ORM
-------------

[](#usage-for-orm)

Create query builder as usual. Dont forget about `orderBy` and `maxResults`.

```
$testEntityRepository = $this->entityManager->getRepository(TestEntity::class);
$qb = $testEntityRepository->createQueryBuilder('t')
    ->orderBy('t.id', 'ASC')
    ->setMaxResults(100)
;

/** @var DoctrineORMCursorPaginator $cursorPaginator */
$cursorPaginator = new DoctrineORMCursorPaginator($qb);

foreach ($cursorPaginator as $testEntity) {
    //...
}
```

DoctrineORMCursorPaginator fetches only 100 records per query, so it never loads the whole dataset into memory at once and can efficiently iterate through even large datasets. See [Memory and the EntityManager](#memory-and-the-entitymanager)below for an important caveat about object hydration.

First sql:

`SELECT ... FROM table ORDER BY id ASC LIMIT 100`

Next:

`SELECT ... FROM table WHERE id > {$id_from_last_record} ORDER BY id ASC LIMIT 100`

You can also specify more order by fields

```
$testEntityRepository = $this->entityManager->getRepository(TestEntity::class);
$qb = $testEntityRepository->createQueryBuilder('t')
    ->select('t.id', 't.createdAt')
    ->orderBy('t.createdAt', 'DESC')
    ->addOrderBy('t.id', 'DESC')
    ->setMaxResults(100)
;

/** @var DoctrineORMCursorPaginator $cursorPaginator */
$cursorPaginator = new DoctrineORMCursorPaginator($qb);

foreach ($cursorPaginator as $testEntity) {
    //...
}
```

You can change hydration mode

```
$cursorPaginator = new DoctrineORMCursorPaginator($qb, AbstractQuery::HYDRATE_ARRAY);
```

And even set query hints

```
$cursorPaginator = new DoctrineORMCursorPaginator(
    queryBuilder: $qb,
    queryHints: [
        'fetchMode' => [
            TestEntity::class => [
                'field' => ClassMetadataInfo::FETCH_EAGER
            ]
        ]
    ]
);
```

You wanna batch? Lets batch:

```
$cursorPaginator = new DoctrineORMCursorPaginator($qb);

foreach ($cursorPaginator->batch() as $entities) {
    foreach ($entities as $testEntity) {
        $cnt++;
    }
}
```

By default batch size equals to `maxResults` but you can also specify desired amount by yourself:

```
$myBatchSize = 1000;

$cursorPaginator = new DoctrineORMCursorPaginator($qb);

foreach ($cursorPaginator->batch($myBatchSize) as $entities) {
}
```

### Memory and the EntityManager

[](#memory-and-the-entitymanager)

The paginator limits how many rows each query returns, but with the default object hydration (`HYDRATE_OBJECT`) Doctrine keeps every hydrated entity in the EntityManager's identity map. Over a large dataset that map keeps growing, so the per-query limit alone does **not** keep memory flat. When you iterate over many entities, clear the EntityManager periodically (batching makes a natural place to do it):

```
foreach ($cursorPaginator->batch() as $entities) {
    foreach ($entities as $entity) {
        // ... process the entity
    }

    $entityManager->clear(); // detach processed entities and free memory
}
```

Keep in mind that `clear()` detaches **all** managed entities: flush any pending changes before calling it, and don't keep references to entities you still expect to be managed. If you don't need managed objects at all, array hydration avoids the identity map entirely and sidesteps the issue:

```
$cursorPaginator = new DoctrineORMCursorPaginator($qb, AbstractQuery::HYDRATE_ARRAY);
```

Usage for DBAL
--------------

[](#usage-for-dbal)

Just use `DoctrineDBALCursorPaginator` instead.

```
$queryBuilder = $this->connection->createQueryBuilder();

$queryBuilder
    ->select('id', 'name')
    ->from('test')
    ->orderBy('id', 'ASC')
    ->setMaxResults(100)
;

$cursorPaginator = new DoctrineDBALCursorPaginator($queryBuilder);

foreach ($cursorPaginator as $row) {
}
```

DBAL exposes no public getter for the `ORDER BY` clause, so by default the order is read from the query builder via reflection. If you prefer to avoid reflection (or your DBAL version changes its internals), pass the order explicitly. It must mirror the `orderBy()`/`addOrderBy()` calls on the query builder:

```
$queryBuilder
    ->select('id', 'name')
    ->from('test')
    ->orderBy('id', 'ASC')
    ->setMaxResults(100)
;

$cursorPaginator = new DoctrineDBALCursorPaginator($queryBuilder, ['id' => 'ASC']);
```

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance97

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~51 days

Total

2

Last Release

16d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2c85524bc58444ab554ec47663bd1716ef1db841d7e245a4569f67c7da8e2d0c?d=identicon)[wiistriker](/maintainers/wiistriker)

---

Top Contributors

[![wiistriker](https://avatars.githubusercontent.com/u/967817?v=4)](https://github.com/wiistriker "wiistriker (20 commits)")

---

Tags

symfonydoctrinekeyset-paginationdeep-page-problemcursor-based-paginationseek method

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/wiistriker-doctrine-cursor-paginator/health.svg)

```
[![Health](https://phpackages.com/badges/wiistriker-doctrine-cursor-paginator/health.svg)](https://phpackages.com/packages/wiistriker-doctrine-cursor-paginator)
```

###  Alternatives

[api-platform/core

Build a fully-featured hypermedia or GraphQL API in minutes!

2.6k51.2M339](/packages/api-platform-core)[sonata-project/doctrine-orm-admin-bundle

Integrate Doctrine ORM into the SonataAdminBundle

46118.2M166](/packages/sonata-project-doctrine-orm-admin-bundle)[omines/datatables-bundle

Symfony DataTables Bundle with native Doctrine ORM, Elastica and MongoDB support

2841.5M6](/packages/omines-datatables-bundle)[api-platform/symfony

Symfony API Platform integration

384.5M129](/packages/api-platform-symfony)[michaeldegroot/doctrine-encrypt-bundle

Encrypted symfony entity's by verified and standardized libraries

1521.0M1](/packages/michaeldegroot-doctrine-encrypt-bundle)[doctrineencryptbundle/doctrine-encrypt-bundle

Encrypted symfony entity's by verified and standardized libraries

32510.9k](/packages/doctrineencryptbundle-doctrine-encrypt-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
