QueryBuilder::setParameter() breaks if parameter name with ':' is set multiple times #6448

Closed
opened 2026-01-22 15:33:28 +01:00 by admin · 0 comments
Owner

Originally created by @tom93 on GitHub (Apr 15, 2020).

Bug Report

Q A
BC Break technically yes, broke in 2.3.0 commit 1635e0af4
Version 2.3.0, v2.7.2, 2.8.x @ 3c91792dd, master @ 8c259ea5c

Summary

QueryBuilder::setParameter() breaks if a parameter name that starts with : is set multiple times.
(Parameter names that don't start with a colon can be set multiple times without issues, and normally it is allowed to prefix a parameter name with a colon.)

Current behavior

An exception is thrown when the query is executed:

Doctrine\ORM\Query\QueryException: Too many parameters

How to reproduce

// without colon (succeeds)
$qb = $em->createQueryBuilder();
$qb
    ->select('u')
    ->from('User', 'u')
    ->where('u.id = :id')
    ->setParameter('id', 1)  // <--
    ->setParameter('id', 1); // <--
dump($qb->getQuery()->getResult());
// success
// with colon (throws exception)
$qb = $em->createQueryBuilder();
$qb
    ->select('u')
    ->from('User', 'u')
    ->where('u.id = :id')
    ->setParameter(':id', 1)  // <--
    ->setParameter(':id', 1); // <--
dump($qb->getQuery()->getResult());
// Doctrine\ORM\Query\QueryException: Too many parameters: the query defines 1 parameters and you bound 2 in [...]

Expected behavior

The second snippet (with colon) should succeed and produce the same result as the first snippet.

Cause

setParameter($key, ...) checks if $key already exists using getParameter($key), which compares $key to the names of the existing Query\Parameter objects:
8c259ea5cb/lib/Doctrine/ORM/QueryBuilder.php (L606)

When $key starts with a colon, the comparison always fails because the names of the existing Query\Parameter objects are trimmed of colons in the constructor:
8c259ea5cb/lib/Doctrine/ORM/Query/Parameter.php (L49)

Fix

getParameter() should trim colons from $key to match the behaviour of the Query\Parameter constructor.
To avoid code duplication, the existing trimming code could be moved to a separate function such as Query\Parameter::normalizeName().
(Another approach is for getParameter() create a dummy instance of Query\Parameter to normalize $key, but that's ugly and requires a dummy value and type.)

Pull request

#8107

Background / original use case

When building complex queries with conditional parts, it can be useful to call setParameter() multiple times with the same parameter.
Here is a contrived example that lists users, optionally filtered by a pattern. It can be configured to search for users by first name, last name, or both:

$qb = $em->createQueryBuilder();
$qb
    ->select('u')
    ->from('User', 'u');
if ($searchFirstName) {
    $qb
        ->orWhere('u.first_name LIKE :pattern')
        ->setParameter('pattern', $pattern);
}
if ($searchLastName) {
    $qb
        ->orWhere('u.last_name LIKE :pattern')
        ->setParameter('pattern', $pattern);
}

Note that the setParameter() calls cannot be combined and moved outside the if statements, because then when both conditions are false the parameter would be unused, resulting in the following error:

Doctrine\ORM\Query\QueryException: Too many parameters: the query defines 0 parameters and you bound 1 in [...]

The QueryBuilder documentation doesn't use leading colons in parameter names passed to setParameter(), but they are supported and are popular in some projects (maybe the practice has been adopted from PDO).

Originally created by @tom93 on GitHub (Apr 15, 2020). ### Bug Report | Q | A |------------ | ------ | BC Break | technically yes, broke in 2.3.0 commit [1635e0af4](https://github.com/doctrine/orm/commit/1635e0af4b06ef3015205563b59b505ae3fac69d#diff-6e1fe274092a9dc90ad2f6728df45c6aL358) | Version | 2.3.0, v2.7.2, 2.8.x @ 3c91792dd, master @ 8c259ea5c #### Summary `QueryBuilder::setParameter()` breaks if a parameter name that starts with `:` is set multiple times. (Parameter names that don't start with a colon can be set multiple times without issues, and normally it is allowed to prefix a parameter name with a colon.) #### Current behavior An exception is thrown when the query is executed: > Doctrine\ORM\Query\QueryException: Too many parameters #### How to reproduce ```php // without colon (succeeds) $qb = $em->createQueryBuilder(); $qb ->select('u') ->from('User', 'u') ->where('u.id = :id') ->setParameter('id', 1) // <-- ->setParameter('id', 1); // <-- dump($qb->getQuery()->getResult()); // success ``` ```php // with colon (throws exception) $qb = $em->createQueryBuilder(); $qb ->select('u') ->from('User', 'u') ->where('u.id = :id') ->setParameter(':id', 1) // <-- ->setParameter(':id', 1); // <-- dump($qb->getQuery()->getResult()); // Doctrine\ORM\Query\QueryException: Too many parameters: the query defines 1 parameters and you bound 2 in [...] ``` #### Expected behavior The second snippet (with colon) should succeed and produce the same result as the first snippet. #### Cause `setParameter($key, ...)` checks if $key already exists using `getParameter($key)`, which compares $key to the names of the existing Query\Parameter objects: https://github.com/doctrine/orm/blob/8c259ea5cb632dbb57001b2262048ae7fa52b102/lib/Doctrine/ORM/QueryBuilder.php#L606 When $key starts with a colon, the comparison always fails because the names of the existing Query\Parameter objects are trimmed of colons in the constructor: https://github.com/doctrine/orm/blob/8c259ea5cb632dbb57001b2262048ae7fa52b102/lib/Doctrine/ORM/Query/Parameter.php#L49 #### Fix `getParameter()` should trim colons from $key to match the behaviour of the Query\Parameter constructor. To avoid code duplication, the existing trimming code could be moved to a separate function such as `Query\Parameter::normalizeName()`. (Another approach is for `getParameter()` create a dummy instance of Query\Parameter to normalize $key, but that's ugly and requires a dummy value and type.) #### Pull request #8107 #### Background / original use case <details> When building complex queries with conditional parts, it can be useful to call `setParameter()` multiple times with the same parameter. Here is a contrived example that lists users, optionally filtered by a pattern. It can be configured to search for users by first name, last name, or both: ```php $qb = $em->createQueryBuilder(); $qb ->select('u') ->from('User', 'u'); if ($searchFirstName) { $qb ->orWhere('u.first_name LIKE :pattern') ->setParameter('pattern', $pattern); } if ($searchLastName) { $qb ->orWhere('u.last_name LIKE :pattern') ->setParameter('pattern', $pattern); } ``` Note that the `setParameter()` calls cannot be combined and moved outside the `if` statements, because then when both conditions are false the parameter would be unused, resulting in the following error: ``` Doctrine\ORM\Query\QueryException: Too many parameters: the query defines 0 parameters and you bound 1 in [...] ``` The [QueryBuilder documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/query-builder.html) doesn't use leading colons in parameter names passed to `setParameter()`, but they are supported and are popular in some projects (maybe the practice has been adopted from [PDO](https://www.php.net/manual/en/pdostatement.bindparam.php)). </details>
admin closed this issue 2026-01-22 15:33:29 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6448