facile-it/doctrine-mysql-come-back
A Doctrine DBAL extension that automatically reconnects to MySQL after connection loss and retries failed queries, helping long-running Laravel/PHP apps and workers recover from dropped database connections without manual reconnection handling.
Install with Composer and check the DBAL compatibility matrix first — the package tracks Doctrine DBAL majors, and recent releases target DBAL 3/4:
composer require facile-it/doctrine-mysql-come-back
You only need two things: tell DBAL to use the package's Connection wrapper, and tell the wrapper how many times to retry.
use Doctrine\DBAL\DriverManager;
use Facile\DoctrineMySQLComeBack\Doctrine\DBAL\Connection;
$connection = DriverManager::getConnection([
'driver' => 'pdo_mysql',
'host' => '127.0.0.1',
'dbname' => 'app',
'user' => 'app',
'password' => 'secret',
'wrapperClass' => Connection::class,
'driverOptions' => [
'x_reconnect_attempts' => 3,
],
]);
The first use case is almost always a long-running process — Symfony Messenger workers, doctrine:migrations:migrate, cron jobs, or queue consumers — that sits idle longer than MySQL's wait_timeout and then dies on the next query with SQLSTATE[HY000]: General error: 2006 MySQL server has gone away. Add the wrapper class, set a small retry count, and those exceptions disappear for queries that are safe to replay.
Where to look first in the source: src/Doctrine/DBAL/Connection.php. Read the overridden executeQuery() / executeStatement() / beginTransaction() / commit() / rollBack() / connect() methods and the constructor that reads x_reconnect_attempts from driverOptions. That tells you exactly which operations are protected and which are not.
Symfony / DoctrineBundle. Configure it on the DBAL connection rather than replacing the connection manually:
# config/packages/doctrine.yaml
doctrine:
dbal:
wrapper_class: Facile\DoctrineMySQLComeBack\Doctrine\DBAL\Connection
options:
x_reconnect_attempts: 3
options maps to DBAL driverOptions, so x_reconnect_attempts lands where the wrapper expects it. Set it per connection if you have multiple doctrine.dbal.connections.
Doctrine ORM. No special handling is needed beyond the DBAL connection: pass the wrapped connection to EntityManager as usual. The retry lives at the DBAL query level, so ORM repositories, DQL, and EntityManager::getConnection()->executeStatement() all benefit automatically.
Worker / daemon pattern. Treat the library as a safety net, not a lifecycle strategy:
foreach ($messages as $message) {
try {
$handler->handle($message);
} finally {
$entityManager->clear(); // avoid stale identity map between jobs
}
}
Keep retries low (1–3). If a consumer is idle for minutes, the reconnect should happen on the first query of the next job, not after five retries of the same query.
Reinitializing session state. MySQL loses session-scoped state on reconnect (SET time_zone, SET NAMES, SET SESSION sql_mode, temporary tables, user variables). If your app depends on those, subclass the package connection and re-apply them after connect():
class AppConnection extends \Facile\DoctrineMySQLComeBack\Doctrine\DBAL\Connection
{
public function connect(): bool
{
$connected = parent::connect();
if ($connected) {
$this->executeStatement("SET time_zone = '+00:00'");
}
return $connected;
}
}
Use this subclass as wrapperClass — it is the cleanest extension point.
Observability. If you want to know when a reconnect actually happened, override connect() in a subclass and log/increment a metric there, or register a DBAL middleware around the query. Do not rely on silently swallowing the original exception: when retries are exhausted, the original driver exception should surface so your error tracking sees it.
Testing the retry path. From a second session, run KILL <connection_id> (find it with SELECT CONNECTION_ID()), then issue a query and assert it succeeds. Alternatively, create a dedicated test database/user with wait_timeout=1, sleep, and query. Avoid doing this against shared CI databases — killing connections is easy to make flaky.
wrapperClass but forget x_reconnect_attempts, nothing changes. Always set it explicitly.2006 is often raising wait_timeout/interactive_timeout, checking max_allowed_packet, and ensuring the network/proxy (HAProxy, RDS proxy, PgBouncer-style MySQL proxies) isn't dropping idle connections. Use the package to survive the remaining edge cases.beginTransaction() / commit() block. Keep transactions short, and treat a reconnect during a transaction as an application error.max_allowed_packet) are not the same problem and generally won't be retried.$connection->getNativeConnection() and using PDO directly, or issuing queries through a different connection object, skips the retry logic entirely.PDO::ATTR_PERSISTENT, cached prepared statements and stale handles can misbehave after reconnect. If you see oddities, disable persistence and retest.Connection constructor signatures and EventManager handling; older releases of this package do not support newer DBAL. Pin your DBAL major and verify the package's compatibility table before upgrading either.$entityManager->clear(), connection health checks (SELECT 1) at job boundaries, and memory monitoring. Reconnection fixes the socket, not a bloated identity map.How can I help you explore Laravel packages today?