-
-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathConnectionRecoveryHandler.php
More file actions
66 lines (56 loc) · 2 KB
/
ConnectionRecoveryHandler.php
File metadata and controls
66 lines (56 loc) · 2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
declare(strict_types=1);
namespace Yiisoft\Db\Driver\Pdo;
use Closure;
use Throwable;
use Yiisoft\Db\Command\CommandInterface;
use Yiisoft\Db\Exception\ConnectionException;
use Yiisoft\Db\Exception\Exception;
/**
* Default retry handler that implements automatic connection recovery on the first execution failure.
*
* Detects {@see ConnectionException} errors and attempts to reconnect and re-prepare the statement before retrying.
* No retry is performed when:
* - it is not the first attempt,
* - the error is not a {@see ConnectionException},
* - a transaction is active (reconnecting would silently roll it back),
* - the reconnection itself fails.
*
* Set as the default {@see AbstractPdoCommand::$retryHandler}.
* Replace it via {@see CommandInterface::setRetryHandler()} to customize retry behavior.
*
* @psalm-type RetryHandlerClosure = Closure(Exception, int, CommandInterface): bool
*/
final class ConnectionRecoveryHandler
{
public function __construct(private readonly PdoConnectionInterface $db) {}
public function __invoke(Exception $e, int $attempt, CommandInterface $command): bool
{
// Only attempt recovery on the first failure.
if ($attempt !== 0 || !$e instanceof ConnectionException) {
return false;
}
// Reconnecting during an active transaction would silently roll it back.
if ($this->db->getTransaction() !== null) {
return false;
}
// Try to renew the connection.
try {
$this->db->close();
$this->db->open();
$command->cancel(); // resets the PDOStatement so prepare() creates a fresh one
} catch (Throwable) {
return false;
}
// Re-prepare the statement against the new connection, restoring all parameter bindings.
$command->prepare();
return true;
}
/**
* @psalm-return RetryHandlerClosure
*/
public function asClosure(): Closure
{
return $this->__invoke(...);
}
}