Skip to content

Commit

Permalink
Predictable QueryBuilder::executeQuery() and `QueryBuilder::execute…
Browse files Browse the repository at this point in the history
…Statement()`

This deprecates `QueryBuilder::execute()`, because its return type is
unpredictable and raises issues with static analysis tools such as PHPStan.

Instead you should use either `QueryBuilder::executeQuery()` or
`QueryBuilder::executeStatement()`, depending on whether the queryBuilder is a query (SELECT)
or a statement (INSERT, UPDATE, DELETE).

You might also consider the use of the new shortcut methods, such as:

- `fetchAllAssociative()`
- `fetchAllAssociativeIndexed()`
- `fetchAllKeyValue()`
- `fetchAllNumeric()`
- `fetchAssociative()`
- `fetchFirstColumn()`
- `fetchNumeric()`
- `fetchOne()`

This commit is a direct follow-up to #4461
where those shortcut methods where introduced.
  • Loading branch information
PowerKiKi committed Apr 6, 2021
1 parent b2befb0 commit 7b5dd78
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 0 deletions.
17 changes: 17 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ Use `Connection::createSchemaManager()` instead.
The usage of `Connection::$_expr` and `Connection::getExpressionBuilder()` is deprecated.
Use `Connection::createExpressionBuilder()` instead.

## Deprecated `QueryBuilder::execute()`

The usage of `QueryBuilder::execute()` is deprecated. Use either `QueryBuilder::executeQuery()` or
`QueryBuilder::executeStatement()`, depending on whether the queryBuilder is a query (SELECT) or a statement (INSERT,
UPDATE, DELETE).

You might also consider the use of the new shortcut methods, such as:

- `fetchAllAssociative()`
- `fetchAllAssociativeIndexed()`
- `fetchAllKeyValue()`
- `fetchAllNumeric()`
- `fetchAssociative()`
- `fetchFirstColumn()`
- `fetchNumeric()`
- `fetchOne()`

# Upgrade to 3.0

## BC BREAK: leading colon in named parameter names not supported
Expand Down
38 changes: 38 additions & 0 deletions src/Query/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,57 @@ public function fetchFirstColumn(): array
return $this->connection->fetchFirstColumn($this->getSQL(), $this->params, $this->paramTypes);
}

/**
* Executes an SQL query (SELECT) and returns a Result.
*
* @throws Exception
*/
public function executeQuery(): Result
{
return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
}

/**
* Executes an SQL statement and returns the number of affected rows.
*
* Should be used for INSERT, UPDATE and DELETE
*
* @return int The number of affected rows.
*
* @throws Exception
*/
public function executeStatement(): int
{
return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes);
}

/**
* Executes this query using the bound parameters and their types.
*
* @deprecated Use {@link executeQuery()} or {@link executeStatement()} instead.
*
* @return Result|int
*
* @throws Exception
*/
public function execute()
{
if ($this->type === self::SELECT) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4578',
'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.'
);

return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
}

Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4578',
'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.'
);

return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes);
}

Expand Down
69 changes: 69 additions & 0 deletions tests/Query/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\QueryException;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use PHPUnit\Framework\MockObject\MockObject;
Expand Down Expand Up @@ -1302,4 +1303,72 @@ public static function fetchProvider(): iterable
'SELECT id, username FROM user WHERE password = :password AND username != :username AND id != :id',
];
}

/**
* @param list<mixed>|array<string, mixed> $parameters
* @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $parameterTypes
*
* @dataProvider fetchProvider
*/
public function testExecuteQuery(
string $select,
string $from,
string $where,
array $parameters,
array $parameterTypes,
string $expectedSql
): void {
$qb = new QueryBuilder($this->conn);
$mockedResult = $this->createMock(Result::class);

$this->conn->expects(self::once())
->method('executeQuery')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn($mockedResult);

$results = $qb->select($select)
->from($from)
->where($where)
->setParameters($parameters, $parameterTypes)
->executeQuery();

self::assertSame(
$mockedResult,
$results
);
}

public function testExecuteStatement(): void
{
$qb = new QueryBuilder($this->conn);
$mockedResult = 123;
$expectedSql = 'UPDATE users SET foo = ?, bar = ? WHERE bar = 1';

$parameters = [
'foo' => 'jwage',
'bar' => false,
];

$parameterTypes = [
'foo' => Types::STRING,
'bar' => Types::BOOLEAN,
];

$this->conn->expects(self::once())
->method('executeStatement')
->with($expectedSql, $parameters, $parameterTypes)
->willReturn($mockedResult);

$results = $qb->update('users')
->set('foo', '?')
->set('bar', '?')
->where('bar = 1')
->setParameters($parameters, $parameterTypes)
->executeStatement();

self::assertSame(
$mockedResult,
$results
);
}
}

0 comments on commit 7b5dd78

Please sign in to comment.