Skip to content

Commit 4f578be

Browse files
authored
Merge pull request #4136 from morozov/exception-converter
Move the logic of driver exception conversion into a separate interface
2 parents 7f6cafc + 23c88b4 commit 4f578be

36 files changed

+1025
-880
lines changed

UPGRADE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Upgrade to 3.0
22

3+
## BC BREAK: Changes in driver-level exception handling
4+
5+
1. The `convertException()` method has been removed from the `Driver` interface. The logic of exception conversion has been moved to the `ExceptionConverter` interface. The drivers now must implement the `getExceptionConverter()` method.
6+
2. The `driverException()` and `driverExceptionDuringQuery()` factory methods have been removed from the `DBALException` class.
7+
3. Non-driver exceptions (e.g. exceptions of type `Error`) are no longer wrapped in a `DBALException`.
8+
39
## BC BREAK: More driver-level methods are allowed to throw a Driver\Exception.
410

511
The following driver-level methods are allowed to throw a Driver\Exception:

phpstan.neon.dist

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ parameters:
88
reportUnmatchedIgnoredErrors: false
99
checkMissingIterableValueType: false
1010
checkGenericClassInNonGenericObjectType: false
11-
earlyTerminatingMethodCalls:
12-
Doctrine\DBAL\Connection:
13-
- handleDriverException
14-
- handleExceptionDuringQuery
1511
ignoreErrors:
1612
# removing it would be BC break
1713
- '~^Constructor of class Doctrine\\DBAL\\Schema\\Table has an unused parameter \$idGeneratorType\.\z~'

src/Connection.php

Lines changed: 95 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Doctrine\DBAL\Cache\CacheException;
1010
use Doctrine\DBAL\Cache\CachingResult;
1111
use Doctrine\DBAL\Cache\QueryCacheProfile;
12+
use Doctrine\DBAL\Driver\API\ExceptionConverter;
1213
use Doctrine\DBAL\Driver\Connection as DriverConnection;
1314
use Doctrine\DBAL\Driver\Exception as DriverException;
1415
use Doctrine\DBAL\Driver\Result as DriverResult;
@@ -26,12 +27,18 @@
2627
use Traversable;
2728

2829
use function array_key_exists;
30+
use function array_map;
2931
use function assert;
32+
use function bin2hex;
3033
use function count;
3134
use function implode;
3235
use function is_int;
36+
use function is_resource;
3337
use function is_string;
38+
use function json_encode;
3439
use function key;
40+
use function preg_replace;
41+
use function sprintf;
3542

3643
/**
3744
* A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
@@ -114,6 +121,9 @@ class Connection implements DriverConnection
114121
*/
115122
private $platform;
116123

124+
/** @var ExceptionConverter|null */
125+
private $exceptionConverter;
126+
117127
/**
118128
* The schema manager.
119129
*
@@ -284,7 +294,7 @@ public function connect()
284294
try {
285295
$this->_conn = $this->_driver->connect($this->params);
286296
} catch (DriverException $e) {
287-
throw DBALException::driverException($this->_driver, $e);
297+
throw $this->convertException($e);
288298
}
289299

290300
$this->transactionNestingLevel = 0;
@@ -467,8 +477,8 @@ public function fetchAssociative(string $query, array $params = [], array $types
467477
{
468478
try {
469479
return $this->executeQuery($query, $params, $types)->fetchAssociative();
470-
} catch (Throwable $e) {
471-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
480+
} catch (DriverException $e) {
481+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
472482
}
473483
}
474484

@@ -488,8 +498,8 @@ public function fetchNumeric(string $query, array $params = [], array $types = [
488498
{
489499
try {
490500
return $this->executeQuery($query, $params, $types)->fetchNumeric();
491-
} catch (Throwable $e) {
492-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
501+
} catch (DriverException $e) {
502+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
493503
}
494504
}
495505

@@ -509,8 +519,8 @@ public function fetchOne(string $query, array $params = [], array $types = [])
509519
{
510520
try {
511521
return $this->executeQuery($query, $params, $types)->fetchOne();
512-
} catch (Throwable $e) {
513-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
522+
} catch (DriverException $e) {
523+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
514524
}
515525
}
516526

@@ -771,8 +781,8 @@ public function fetchAllNumeric(string $query, array $params = [], array $types
771781
{
772782
try {
773783
return $this->executeQuery($query, $params, $types)->fetchAllNumeric();
774-
} catch (Throwable $e) {
775-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
784+
} catch (DriverException $e) {
785+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
776786
}
777787
}
778788

@@ -791,8 +801,8 @@ public function fetchAllAssociative(string $query, array $params = [], array $ty
791801
{
792802
try {
793803
return $this->executeQuery($query, $params, $types)->fetchAllAssociative();
794-
} catch (Throwable $e) {
795-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
804+
} catch (DriverException $e) {
805+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
796806
}
797807
}
798808

@@ -811,8 +821,8 @@ public function fetchFirstColumn(string $query, array $params = [], array $types
811821
{
812822
try {
813823
return $this->executeQuery($query, $params, $types)->fetchFirstColumn();
814-
} catch (Throwable $e) {
815-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
824+
} catch (DriverException $e) {
825+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
816826
}
817827
}
818828

@@ -835,8 +845,8 @@ public function iterateNumeric(string $query, array $params = [], array $types =
835845
while (($row = $result->fetchNumeric()) !== false) {
836846
yield $row;
837847
}
838-
} catch (Throwable $e) {
839-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
848+
} catch (DriverException $e) {
849+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
840850
}
841851
}
842852

@@ -859,8 +869,8 @@ public function iterateAssociative(string $query, array $params = [], array $typ
859869
while (($row = $result->fetchAssociative()) !== false) {
860870
yield $row;
861871
}
862-
} catch (Throwable $e) {
863-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
872+
} catch (DriverException $e) {
873+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
864874
}
865875
}
866876

@@ -883,8 +893,8 @@ public function iterateColumn(string $query, array $params = [], array $types =
883893
while (($value = $result->fetchOne()) !== false) {
884894
yield $value;
885895
}
886-
} catch (Throwable $e) {
887-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
896+
} catch (DriverException $e) {
897+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
888898
}
889899
}
890900

@@ -899,11 +909,7 @@ public function iterateColumn(string $query, array $params = [], array $types =
899909
*/
900910
public function prepare(string $sql): DriverStatement
901911
{
902-
try {
903-
return new Statement($sql, $this);
904-
} catch (Throwable $e) {
905-
$this->handleExceptionDuringQuery($e, $sql);
906-
}
912+
return new Statement($sql, $this);
907913
}
908914

909915
/**
@@ -952,8 +958,8 @@ public function executeQuery(
952958
}
953959

954960
return new Result($result, $this);
955-
} catch (Throwable $e) {
956-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
961+
} catch (DriverException $e) {
962+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
957963
} finally {
958964
if ($logger !== null) {
959965
$logger->stopQuery();
@@ -1010,6 +1016,9 @@ public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qc
10101016
return new Result($result, $this);
10111017
}
10121018

1019+
/**
1020+
* @throws DBALException
1021+
*/
10131022
public function query(string $sql): DriverResult
10141023
{
10151024
$connection = $this->getWrappedConnection();
@@ -1021,8 +1030,8 @@ public function query(string $sql): DriverResult
10211030

10221031
try {
10231032
return $connection->query($sql);
1024-
} catch (Throwable $e) {
1025-
$this->handleExceptionDuringQuery($e, $sql);
1033+
} catch (DriverException $e) {
1034+
throw $this->convertExceptionDuringQuery($e, $sql);
10261035
} finally {
10271036
if ($logger !== null) {
10281037
$logger->stopQuery();
@@ -1067,15 +1076,18 @@ public function executeUpdate(string $query, array $params = [], array $types =
10671076
}
10681077

10691078
return $connection->exec($query);
1070-
} catch (Throwable $e) {
1071-
$this->handleExceptionDuringQuery($e, $query, $params, $types);
1079+
} catch (DriverException $e) {
1080+
throw $this->convertExceptionDuringQuery($e, $query, $params, $types);
10721081
} finally {
10731082
if ($logger !== null) {
10741083
$logger->stopQuery();
10751084
}
10761085
}
10771086
}
10781087

1088+
/**
1089+
* @throws DBALException
1090+
*/
10791091
public function exec(string $statement): int
10801092
{
10811093
$connection = $this->getWrappedConnection();
@@ -1087,8 +1099,8 @@ public function exec(string $statement): int
10871099

10881100
try {
10891101
return $connection->exec($statement);
1090-
} catch (Throwable $e) {
1091-
$this->handleExceptionDuringQuery($e, $statement);
1102+
} catch (DriverException $e) {
1103+
throw $this->convertExceptionDuringQuery($e, $statement);
10921104
} finally {
10931105
if ($logger !== null) {
10941106
$logger->stopQuery();
@@ -1573,15 +1585,12 @@ private function getBindingInfo($value, $type)
15731585
/**
15741586
* Resolves the parameters to a format which can be displayed.
15751587
*
1576-
* @internal This is a purely internal method. If you rely on this method, you are advised to
1577-
* copy/paste the code as this method may change, or be removed without prior notice.
1578-
*
15791588
* @param mixed[] $params
15801589
* @param array<int|string|null> $types
15811590
*
15821591
* @return mixed[]
15831592
*/
1584-
public function resolveParams(array $params, array $types)
1593+
private function resolveParams(array $params, array $types): array
15851594
{
15861595
$resolvedParams = [];
15871596

@@ -1633,53 +1642,73 @@ public function createQueryBuilder()
16331642
*
16341643
* @param array<mixed> $params
16351644
* @param array<int|string|null> $types
1636-
*
1637-
* @throws DBALException
1638-
*
1639-
* @psalm-return never-return
16401645
*/
1641-
public function handleExceptionDuringQuery(Throwable $e, string $sql, array $params = [], array $types = []): void
1642-
{
1643-
$this->throw(
1644-
DBALException::driverExceptionDuringQuery(
1645-
$this->_driver,
1646-
$e,
1647-
$sql,
1646+
final public function convertExceptionDuringQuery(
1647+
DriverException $e,
1648+
string $sql,
1649+
array $params = [],
1650+
array $types = []
1651+
): DBALException {
1652+
$message = "An exception occurred while executing '" . $sql . "'";
1653+
1654+
if (count($params) > 0) {
1655+
$message .= ' with params ' . $this->formatParameters(
16481656
$this->resolveParams($params, $types)
1649-
)
1650-
);
1657+
);
1658+
}
1659+
1660+
$message .= ":\n\n" . $e->getMessage();
1661+
1662+
return $this->handleDriverException($e, $message);
16511663
}
16521664

16531665
/**
16541666
* @internal
1655-
*
1656-
* @throws DBALException
1657-
*
1658-
* @psalm-return never-return
16591667
*/
1660-
public function handleDriverException(Throwable $e): void
1668+
final public function convertException(DriverException $e): DBALException
16611669
{
1662-
$this->throw(
1663-
DBALException::driverException(
1664-
$this->_driver,
1665-
$e
1666-
)
1670+
return $this->handleDriverException(
1671+
$e,
1672+
'An exception occurred in driver: ' . $e->getMessage()
16671673
);
16681674
}
16691675

16701676
/**
1671-
* @internal
1672-
*
1673-
* @throws DBALException
1677+
* Returns a human-readable representation of an array of parameters.
1678+
* This properly handles binary data by returning a hex representation.
16741679
*
1675-
* @psalm-return never-return
1680+
* @param mixed[] $params
16761681
*/
1677-
private function throw(DBALException $e): void
1682+
private function formatParameters(array $params): string
1683+
{
1684+
return '[' . implode(', ', array_map(static function ($param): string {
1685+
if (is_resource($param)) {
1686+
return (string) $param;
1687+
}
1688+
1689+
$json = @json_encode($param);
1690+
1691+
if (! is_string($json) || $json === 'null' && is_string($param)) {
1692+
// JSON encoding failed, this is not a UTF-8 string.
1693+
return sprintf('"%s"', preg_replace('/.{2}/', '\\x$0', bin2hex($param)));
1694+
}
1695+
1696+
return $json;
1697+
}, $params)) . ']';
1698+
}
1699+
1700+
private function handleDriverException(DriverException $driverException, string $message): DBALException
16781701
{
1679-
if ($e instanceof ConnectionLost) {
1702+
if ($this->exceptionConverter === null) {
1703+
$this->exceptionConverter = $this->_driver->getExceptionConverter();
1704+
}
1705+
1706+
$exception = $this->exceptionConverter->convert($message, $driverException);
1707+
1708+
if ($exception instanceof ConnectionLost) {
16801709
$this->close();
16811710
}
16821711

1683-
throw $e;
1712+
return $exception;
16841713
}
16851714
}

src/Connections/PrimaryReadReplicaConnection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ public function ensureConnectedToReplica(): bool
220220
* @param string $connectionName
221221
*
222222
* @return DriverConnection
223+
*
224+
* @throws DBALException
223225
*/
224226
protected function connectTo($connectionName)
225227
{
@@ -230,7 +232,7 @@ protected function connectTo($connectionName)
230232
try {
231233
return $this->_driver->connect($connectionParams);
232234
} catch (DriverException $e) {
233-
throw DBALException::driverException($this->_driver, $e);
235+
throw $this->convertException($e);
234236
}
235237
}
236238

0 commit comments

Comments
 (0)