Skip to content

[Pagination] Fix the Doctrine ORM count when a parameter is only used by the SELECT or the ORDER BY - #4193

Merged
Kocal merged 1 commit into
symfony:3.xfrom
ker0x:pagination-orm-count-unused-parameters
Nov 3, 2026
Merged

Kocal merged 1 commit into
symfony:3.xfrom
ker0x:pagination-orm-count-unused-parameters

Conversation

@ker0x

@ker0x ker0x commented Nov 2, 2026

Copy link
Copy Markdown
Contributor
Q A
Bug fix? yes
New feature? no
Deprecations? no
Documentation? no
Issues Fix #4191
License MIT

DoctrineOrmAdapter::count() replaces the SELECT with COUNT(alias) and removes the ORDER BY, but it keeps every bound parameter. When a parameter is only used by the removed parts, for example a scalar subquery or a HIDDEN sort key added with addSelect(), or a CASE expression in orderBy(), the count query no longer uses it and Doctrine rejects it:

$queryBuilder
    ->select('a')
    ->addSelect('(SELECT COUNT(b.id) FROM App\Entity\Book b WHERE b.author = a AND b.price > :minPrice) AS expensiveBooks')
    ->from(Author::class, 'a')
    ->setParameter('minPrice', 10);

$pagination->getItems();      // works
$pagination->getTotalItems(); // QueryException: Too many parameters: the query defines 0 parameters and you bound 1

The count query now keeps only the parameters that its DQL still uses. It does this the same way as Doctrine's own Paginator (unbindUnusedQueryParams()): it parses the query and reads the parameter mappings. Parameters used by the WHERE clause, including ones shared with the SELECT, stay bound. The query is parsed only when it has parameters.

Count query Before After
Parameter only used by a subquery in the SELECT QueryException Total
Parameter only used by the ORDER BY QueryException Total
Parameter used by the WHERE clause Total Total

The DBAL adapter is not affected: it wraps the whole query in a subquery, so every parameter stays in use.

Checks:

  • The three new tests (named parameter in the SELECT, parameter in the ORDER BY, positional parameters) fail on 3.x with the Too many parameters error, and pass with the fix.
  • Also probed: a parameter shared by the SELECT and the WHERE clause, and a collection join (COUNT(DISTINCT)) with a SELECT-only parameter.
  • The full Pagination suite passes, except UXPaginationExtensionTest::testNamedPaginatorsInheritRootConfigurationAndOverrideSelectedValues, which fails the same way on 3.x with symfony/dependency-injection 8.2.x-dev.
  • The adapter tests pass with --prefer-lowest (doctrine/orm 3.0.0-beta1, doctrine/dbal 4.4.0).

@carsonbot carsonbot added Bug Bug Fix Pagination Status: Needs Review Needs to be reviewed labels Nov 2, 2026
@ker0x
ker0x force-pushed the pagination-orm-count-unused-parameters branch from 7c0996d to 39207af Compare November 3, 2026 17:53
… by the SELECT or the ORDER BY

| Q              | A
| -------------- | ---
| Bug fix?       | yes
| New feature?   | no
| Deprecations?  | no
| Documentation? | no
| Issues         | Fix symfony#4191
| License        | MIT

`DoctrineOrmAdapter::count()` replaces the SELECT with `COUNT(alias)` and removes the ORDER BY, but it keeps every bound parameter. When a parameter is only used by the removed parts, for example a scalar subquery or a `HIDDEN` sort key added with `addSelect()`, or a `CASE` expression in `orderBy()`, the count query no longer uses it and Doctrine rejects it:

```php
$queryBuilder
    ->select('a')
    ->addSelect('(SELECT COUNT(b.id) FROM App\Entity\Book b WHERE b.author = a AND b.price > :minPrice) AS expensiveBooks')
    ->from(Author::class, 'a')
    ->setParameter('minPrice', 10);

$pagination->getItems();      // works
$pagination->getTotalItems(); // QueryException: Too many parameters: the query defines 0 parameters and you bound 1
```

The count query now keeps only the parameters that its DQL still uses. It does this the same way as Doctrine's own `Paginator` (`unbindUnusedQueryParams()`): it parses the query and reads the parameter mappings. Parameters used by the WHERE clause, including ones shared with the SELECT, stay bound. The query is parsed only when it has parameters.

| Count query                                          | Before            | After       |
| ---------------------------------------------------- | ----------------- | ----------- |
| Parameter only used by a subquery in the SELECT      | `QueryException`  | Total       |
| Parameter only used by the ORDER BY                  | `QueryException`  | Total       |
| Parameter used by the WHERE clause                   | Total             | Total       |

The DBAL adapter is not affected: it wraps the whole query in a subquery, so every parameter stays in use.

Checks:

- The three new tests (named parameter in the SELECT, parameter in the ORDER BY, positional parameters) fail on 3.x with the `Too many parameters` error, and pass with the fix.
- Also probed: a parameter shared by the SELECT and the WHERE clause, and a collection join (`COUNT(DISTINCT)`) with a SELECT-only parameter.
- The full Pagination suite passes, except `UXPaginationExtensionTest::testNamedPaginatorsInheritRootConfigurationAndOverrideSelectedValues`, which fails the same way on 3.x with symfony/dependency-injection 8.2.x-dev.
- The adapter tests pass with `--prefer-lowest` (doctrine/orm 3.0.0-beta1, doctrine/dbal 4.4.0).
@Kocal
Kocal force-pushed the pagination-orm-count-unused-parameters branch from 39207af to 7e389c6 Compare November 3, 2026 19:50
@Kocal Kocal self-assigned this Nov 3, 2026
@Kocal Kocal removed their assignment Nov 3, 2026
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Nov 3, 2026
@Kocal

Kocal commented Nov 3, 2026

Copy link
Copy Markdown
Member

Thank you @ker0x.

@Kocal
Kocal merged commit 1f50b9e into symfony:3.x Nov 3, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Bug Fix Pagination Status: Reviewed Has been reviewed by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Pagination] DoctrineOrmAdapter::count() fails when a computed column binds parameters

3 participants