Skip to content

Commit

Permalink
Added the functions "createNamedParameter" and "createPositionalParam…
Browse files Browse the repository at this point in the history
…eter" to the class "QueryBuilder"

The two functions "createNamedParameter" and "createPositionalParameter", from the "QueryBuilder" in the package "doctrine/dbal", copied into the ORM "QueryBuilder" class and adapted to ORM.

doctrineGH-9878
  • Loading branch information
Digi92 committed Jul 6, 2022
1 parent f79ec43 commit 00986f9
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
25 changes: 25 additions & 0 deletions docs/en/reference/query-builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,3 +607,28 @@ same query of example 6 written using
->add('from', new Expr\From('User', 'u'))
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Binding Parameters to Placeholders
----------------------------------

It is often not necessary to know about the exact placeholder names
during the building of a query. You can use two helper methods
to bind a value to a placeholder and directly use that placeholder
in your query as a return value:

.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User', 'u')
->where('email = ' . $qb->createNamedParameter($userInputEmail))
;
// SELECT u FROM User u WHERE email = :dcValue1
$qb->select('id')
->from('User', 'u')
->where('email = ' . $qb->createPositionalParameter($userInputEmail))
;
// SELECT u FROM User u WHERE email = ?
77 changes: 77 additions & 0 deletions lib/Doctrine/ORM/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryExpressionVisitor;
Expand Down Expand Up @@ -148,6 +150,13 @@ class QueryBuilder
/** @var int */
protected $lifetime = 0;

/**
* The counter of bound parameters used with {@see createNamedParameter) and {@see createPositionalParameter).
*
* @var int
*/
private $boundCounter = 0;

/**
* Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
*
Expand Down Expand Up @@ -1506,6 +1515,74 @@ public function __toString()
return $this->getDQL();
}

/**
* Creates a new named parameter and bind the value $value to it.
*
* This method provides a shortcut for {@see Statement::bindValue()}
* when using prepared statements.
*
* The parameter $value specifies the value that you want to bind. If
* $placeholder is not provided createNamedParameter() will automatically
* create a placeholder for you. An automatic placeholder will be of the
* name ':dcValue1', ':dcValue2' etc.
*
* Example:
* <code>
* $value = 2;
* $q->eq( 'id', $q->createNamedParameter( $value ) );
* $stmt = $q->executeQuery(); // executed with 'id = 2'
* </code>
*
* @link http://www.zetacomponents.org
*
* @param mixed $value
* @param int|string|Type|null $type
* @param string $placeHolder The name to bind with. The string must start with a colon ':'.
*
* @return string the placeholder name used.
*/
public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
{
if ($placeHolder === null) {
$this->boundCounter++;
$placeHolder = ':dcValue' . $this->boundCounter;
}

$this->setParameter(substr($placeHolder, 1), $value, $type);

return $placeHolder;
}

/**
* Creates a new positional parameter and bind the given value to it.
*
* Attention: If you are using positional parameters with the query builder you have
* to be very careful to bind all parameters in the order they appear in the SQL
* statement , otherwise they get bound in the wrong order which can lead to serious
* bugs in your code.
*
* Example:
* <code>
* $qb = $conn->createQueryBuilder();
* $qb->select('u.*')
* ->from('users', 'u')
* ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
* ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
* </code>
*
* @param mixed $value
* @param int|string|Type|null $type
*
* @return string
*/
public function createPositionalParameter($value, $type = ParameterType::STRING)
{
$this->setParameter($this->boundCounter, $value, $type);
$this->boundCounter++;

return '?';
}

/**
* Deep clones all expression objects in the DQL parts.
*
Expand Down
46 changes: 46 additions & 0 deletions tests/Doctrine/Tests/ORM/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join;
Expand Down Expand Up @@ -1279,4 +1280,49 @@ public function testJoin(): void

self::assertSame('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN Doctrine\Tests\Models\CMS\CmsArticle a0 INNER JOIN Doctrine\Tests\Models\CMS\CmsArticle a1', $builder->getDQL());
}

public function testCreateNamedParameter(): void
{
$qb = $this->entityManager->createQueryBuilder();

$qb->select('u')
->from(CmsUser::class, 'u')
->where(
$qb->expr()->eq('u.name', $qb->createNamedParameter(10, ParameterType::INTEGER))
);

self::assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = :dcValue1', $qb->getDQL());
self::assertEquals(10, $qb->getParameter('dcValue1')->getValue());
self::assertEquals(ParameterType::INTEGER, $qb->getParameter('dcValue1')->getType());
}

public function testCreateNamedParameterCustomPlaceholder(): void
{
$qb = $this->entityManager->createQueryBuilder();

$qb->select('u')
->from(CmsUser::class, 'u')
->where(
$qb->expr()->eq('u.name', $qb->createNamedParameter(10, ParameterType::INTEGER, ':test'))
);

self::assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = :test', $qb->getDQL());
self::assertEquals(10, $qb->getParameter('test')->getValue());
self::assertEquals(ParameterType::INTEGER, $qb->getParameter('test')->getType());
}

public function testCreatePositionalParameter(): void
{
$qb = $this->entityManager->createQueryBuilder();

$qb->select('u')
->from(CmsUser::class, 'u')
->where(
$qb->expr()->eq('u.name', $qb->createPositionalParameter(10, ParameterType::INTEGER))
);

self::assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = ?', $qb->getDQL());
self::assertEquals(10, $qb->getParameter(0)->getValue());
self::assertEquals(ParameterType::INTEGER, $qb->getParameter(0)->getType());
}
}

0 comments on commit 00986f9

Please sign in to comment.