keboola/doctrine-retry-bundle
A Symfony bundle that adds configurable retry logic to Doctrine DBAL connections, helping applications recover from transient database failures and improve resilience with minimal configuration.
Architecture fit — unchanged, still a mismatch with the stated target. The package is a Symfony bundle, not a Laravel package. Its value is concentrated in DependencyInjection\Compiler\ConfigureDbalRetryProxyPass, which runs at Symfony container compile time and rewrites DoctrineBundle's setMiddlewares() call. Laravel has no bundle kernel, no compiler passes, and no DoctrineBundle; a Laravel application only touches Doctrine DBAL if it has opted in via a community ORM bridge. Nothing in 1.2.0 changes this — the release is entirely about widening the Symfony/DoctrineBundle support matrix, i.e. it deepens the bundle's investment in the Symfony integration path we cannot use.
What does port is the small runtime core: Database\Retry\Middleware (a DBAL driver middleware) wrapping the driver in Database\Retry\Driver, which overrides only connect() and routes it through a keboola/retry RetryProxy. That is framework-agnostic DBAL API and can be registered manually against Doctrine\DBAL\Configuration::setMiddlewares(). Realistically, a Laravel team is adopting ~2 of 4 classes and reimplementing the configuration layer; the "interesting part" per the maintainers' own docs is the part that does not survive the port.
Integration feasibility — conditional, and the condition just got more expensive. Installing the bundle as-is now requires the target to be a Symfony 7.4/8.0 app on doctrine-bundle 3.x with DBAL 4.2+. The 1.2.0 release notes are explicit that this is breaking: consumers on Symfony 6.4 / doctrine-bundle 2.x "cannot take this release." Installing the middleware without the bundle still requires: (a) Doctrine DBAL actually present, (b) a service provider that owns middleware ordering — critically, the retry middleware must be prepended so it is outermost and observes failures from every other middleware, (c) a replacement for the driverOptions.x_connect_retries config convention, since nothing reads it without the compiler pass. That is a small but non-trivial piece of custom plumbing plus a bespoke convention the team now owns — and none of it is made easier by 1.2.0.
Technical risk — elevated, and the platform-upgrade surface widened. Four independent reasons now:
setMiddlewares() call. If DoctrineBundle changes how it registers middlewares, this bundle's behavior is dropped with no error — the maintainers say so explicitly and rely on a Toxiproxy integration test as the only guard. Note that 1.2.0 is precisely a DoctrineBundle-major upgrade; the fact that they followed it is reassuring, but it also demonstrates that doctrine-bundle majors are the exact event this fragility is exposed to, and that our retry behavior is only as good as their next sync with DoctrineBundle internals. Any port of this logic inherits the same fragility against our own service provider ordering.^8.4, doctrine/dbal ^4.2, doctrine/doctrine-bundle ^3.0, Symfony ^7.4|^8.0. Most Laravel estates run PHP 8.2/8.3 with DBAL 3.x or 4.0/4.1; most Symfony estates that are not freshly upgraded are on doctrine-bundle 2.x. Composer will hard-fail on the platform requirement before any code is exercised. This is a platform-upgrade dependency disguised as a retry utility, and 1.2.0 added a fourth coupled component to the upgrade.connect() is retried. Queries, transactions, deadlocks, and mid-statement failures are deliberately not retried — correctly, since replaying a statement after a mid-transaction failure is unsafe. Teams often assume "retry bundle" means resilient queries; it does not.Key questions.
keboola/retry as a transitive dependency acceptable, or do we require an in-house RetryProxy equivalent?Stack fit. Fit is good-to-excellent for Symfony 7.4/8.0 + doctrine-bundle 3.x + DBAL 4.2+ on PHP 8.4; it is zero-config there and the compiler pass does the right thing (skips connections with absent or 0 x_connect_retries, prepends per-connection middleware, leaves everything else untouched). 1.2.0 improves fit specifically for teams already on Symfony 8 and stuck behind doctrine-bundle 2.x's var-exporter incompatibility — that unblocking is real, but it is a Symfony-8 problem, not ours. Fit for a Laravel-first stack is unchanged and poor: no bundle, no compiler pass, likely no DBAL, and a PHP version floor that conflicts with the rest of the app. Score this as a Symfony-ecosystem asset being evaluated against a Laravel target.
Migration path. Two distinct paths, and the decision should be made explicitly rather than by accident:
composer require keboola/doctrine-retry-bundle:^1.2, ensure doctrine-bundle is on 3.x, set driverOptions.x_connect_retries per connection, no code changes. Lowest effort, but only if the app is Symfony 7.4/8.0 + doctrine-bundle 3.x + DBAL 4.2 on PHP 8.4. If doctrine-bundle is still on 2.x, Path A is blocked and the doctrine-bundle major must be sequenced first.doctrine/dbal directly (or via an ORM bridge), register a service provider that appends our own retry middleware at the front of Configuration::setMiddlewares(), and replace the x_connect_retries convention with explicit config keys. If the retry primitive is small enough, substituting an in-house decorator removes both the keboola/retry and mirror-governance dependencies; the four-class footprint makes this genuinely viable and should be scoped and compared before committing to Path B.Either path should include a chaos test (Toxiproxy or equivalent network fault injection) — a retry path that is never exercised in staging is not a tested retry path.
Compatibility. Hard floors as of 1.2.0: PHP ^8.4, doctrine/dbal ^4.2, doctrine/doctrine-bundle ^3.0, Symfony ^7.4|^8.0. DBAL 3.x, DBAL 4.0/4.1, Symfony 6.4, and doctrine-bundle 2.x are all out — the last two are newly excluded by this release. Laravel versions that cap PHP below 8.4 will block installation outright. The middleware must be validated against the exact DBAL minor we ship, because the extension point is DBAL driver middleware, and DBAL 4's lazy-connection semantics are what the connect() override actually interacts with — that is the surface most likely to shift in a patch release. Verify with composer why-not before scheduling anything, and check whether a doctrine-bundle 3.x upgrade is transitively required by anything else in the app (it may drag var-exporter or other Symfony 8 components with it).
Sequencing.
1.1.1 explicitly and record it as an unmaintained-line risk; do not allow a floating ^1 constraint to silently jump a major-supported-stack boundary on the next composer update.Maintenance. The repository is a read-only mirror of libs/doctrine-retry-bundle/ in the keboola/platform-libraries monorepo, force-pushed on every green build. Pull requests against the mirror cannot be merged and will be closed; commits made there are overwritten and lost. Practically this means: we cannot patch in place, our contribution path runs through a third-party monorepo we do not control, and any fork we maintain is a permanent divergence. Combined with 0 stars and 0 dependents, there is no external contributor base — we are effectively the only party who would ever fix a bug that affects us. Budget for owning a fork or an in-house equivalent; do not budget for "upstream will handle it."
1.2.0 sharpens this in two ways. First, the release notes name the motivating consumer — "the editor service" — confirming that the release cadence and compatibility matrix are driven by Keboola's internal services, not by external adopters. When our stack and theirs diverge, we lose. Second, a breaking change shipped in a minor version (1.1.1 → 1.2.0, dropping Symfony 6.4 and doctrine-bundle 2.x) signals that patch/minor bumps are not safe to take unattended; pin exact versions and review each release rather than floating a caret range.
Support. Single-vendor, MIT-licensed, no community. Support model is self-support plus best-effort upstream contact at Keboola. Documentation is thin and split across README.md (install, x_connect_retries, test env vars) and AGENTS.md (architecture, monorepo conventions) — adequate for an engineer, insufficient for an auditor. There is still no published compatibility or deprecation policy tying DBAL or doctrine-bundle minor/major releases to bundle releases; 1.2.0 demonstrates the pattern (follow the framework major, accept the break, note it in the changelog) rather than a policy. Assume we discover breakage via CI, not via changelog.
Scaling. Retry-on-connect is cheap per attempt and adds no steady-state overhead. The scaling concern is the opposite direction: during a database outage or failover, every connection attempt in every process retries simultaneously. Without capped attempts plus exponential backoff with jitter, connection retry converts a transient blip into a thundering herd against an already-degraded primary. Also weigh this against infrastructure-level remedies — a connection pooler or managed proxy with its own failover handling may deliver the same reliability with less application-level surface, and is worth comparing explicitly before adopting an application-layer retry middleware.
Failure modes.
x_connect_retries during an outage, as above.keboola/retry dependency: a defect in the retry primitive surfaces as connection failures across every configured connection at once, since the middleware is prepended and outermost by design.composer update in an unrelated sprint can pull that in transitively.Ramp-up. Low for the happy path: enabling it is a per-connection driverOptions.x_connect_retries integer, or a small config block in a ported version. High for the parts that matter: understanding DBAL driver middleware ordering, why prepending is required, why only connect() is safe, and how to reproduce a failure with Toxiproxy. Estimate one engineer-week to get a Laravel port production-ready including the chaos test and instrumentation — and treat the version-floor upgrade (PHP 8.4 / DBAL 4.2 / doctrine-bundle 3.x) as a separate, larger line item if the platform is not already there. 1.2.0 did not reduce that estimate; it added a component to the upgrade checklist.
How can I help you explore Laravel packages today?