efureev/swoole-doctrine-dbal-pgsql-driver
English | Русский
swoole-doctrine-dbal-pgsql-driver
A PostgreSQL connection pool for Doctrine DBAL on Swoole: one physical connection per coroutine,
a coroutine-safe Doctrine\DBAL\Connection wrapper, and a Symfony bundle that moves DoctrineBundle
connections onto the pool with three lines of configuration.
| What you get | How |
|---|---|
| A bounded number of connections per worker at any concurrency | a pool with a semaphore on Swoole\Coroutine\Channel: a connection is leased lazily on the coroutine's first query and returned on kernel.terminate / Coroutine::defer |
| Transactions invisible to neighbouring coroutines | CoroutineSafeConnection: nesting level, rollback-only flag and isolation live on the coroutine's lease, not in the wrapper's shared field |
| Nothing lost, nothing leaked | on release: always ROLLBACK an unfinished transaction, then DISCARD ALL; lost connections are destroyed, a Statement from another coroutine is rejected |
| Real DBAL exceptions | the transport is PDO pgsql under the Swoole hook: SQLSTATE, UniqueConstraintViolationException, DeadlockException, ConnectionLost work exactly as without a pool |
Requirements
| PHP | ≥ 8.5 (ZTS is the production build for Swoole) |
| ext-swoole | ≥ 6.2 built with --enable-swoole-pgsql; SWOOLE_HOOK_PDO_PGSQL in hook_flags (part of SWOOLE_HOOK_ALL) |
PDO pgsql driver |
Swoole's coroutine one (php --ri swoole → coroutine_pgsql => enabled). The stock ext-pdo_pgsql may stay loaded — it must load before swoole so that Swoole re-registers the driver |
| doctrine/dbal | ^4.4 |
| Symfony (optional) | ^8.1 with doctrine/doctrine-bundle ^3 |
The official phpswoole/swoole:6.2.x-php8.5[-zts] images are built with pgsql support.
Installation
composer require efureev/swoole-doctrine-dbal-pgsql-driver
Symfony: quick start
// config/bundles.php
SwooleDoctrinePool\Bridge\Symfony\SwooleDoctrinePoolBundle::class => ['all' => true],
# config/packages/swoole_doctrine_pool.yaml
swoole_doctrine_pool:
connections:
default: ~ # every pool setting at its default
# config/packages/doctrine.yaml — no driver_class / wrapper_class needed, the bundle injects them
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
server_version: '17' # otherwise DoctrineBundle connects to the DB while building the platform
idle_connection_ttl: 0 # idle handling belongs to the pool
Verify: bin/console debug:config swoole_doctrine_pool prints the defaults,
bin/console debug:container database_connection shows CoroutineSafeConnection.
If your Swoole runtime dispatches the swoole.* events, the rest is automatic: the connection goes back
to the pool on kernel.terminate, pools are closed on swoole.worker_stop. Without them —
see docs/symfony.md.
Without Symfony
$registry = new PoolRegistry(logger: $logger); // one per worker
$config = new Configuration();
$config->setMiddlewares([new PoolDriverMiddleware($registry)]);
$connection = DriverManager::getConnection([
'driverClass' => SwooleDoctrinePool\Driver\Driver::class,
'wrapperClass' => SwooleDoctrinePool\DBAL\CoroutineSafeConnection::class,
'host' => '127.0.0.1', 'dbname' => 'app', 'user' => 'app', 'password' => 'secret',
'serverVersion' => '17',
'driverOptions' => ['pool' => ['size' => 20]],
], $config);
$server->on('workerStop', static fn() => $registry->closeAll());
Details: docs/standalone.md.
Things to remember
- A lease lives until the end of the request (
kernel.terminate, an explicit$connection->close(), or the end of the coroutine), not until the end of an SQL statement — that is what keepslastInsertId()and transactions on one session. Consequentlysizeis also the cap on concurrent DB requests per worker. - A child coroutine (
go()inside a handler) gets its own connection and is outside the parent's transaction. - Do not touch the database before the worker starts: a pool inherited across fork is discarded with a warning.
size × number of workersmust fit into Postgresmax_connections.- The package makes DBAL coroutine-safe. The ORM UnitOfWork is still the state of one
EntityManager: concurrent coroutines need an EntityManager per coroutine. reset_on_release: rollback_onlysaves a round-trip, butSET, advisory locks and temp tables survive the request. The default isdiscard.
Documentation
| Document | Contents |
|---|---|
| docs/index.md | table of contents |
| docs/getting-started.md | requirements, installation, wiring, first load check |
| docs/use-cases.md | 15 scenarios: HTTP handlers, transactions, ORM, parallel queries, daemons, console, multiple databases, shutdown, warm-up, failover, metrics, large results, session state, tests, limits |
| docs/configuration.md | every swoole_doctrine_pool.* key and what it means; sizing for high load |
| docs/symfony.md | what the bundle does, listeners, url:/DATABASE_URL, monolog |
| docs/standalone.md | usage without Symfony |
| docs/architecture.md | components, lease, acquire/release, data-safety guarantees |
| docs/events.md | PSR-14 events |
| docs/stats.md | PoolStats, metrics hook, alerts |
| docs/testing.md | testing an application with the pool; the package's own tests |
| docs/troubleshooting.md | symptom → cause |
| UPGRADE-3.0.md | migration from v2 (Russian) |
| CHANGELOG.md | change history (Russian) |
| tests/Integration/README.md | how the integration tests run |
Development
composer install --ignore-platform-req=ext-swoole # locally without Swoole: phpcs, psalm, unit
composer ci # phpcs + psalm + unit
composer up && composer integration # integration tests in docker (Postgres 17)
composer down
Integration image matrix: phpswoole/swoole:6.2.3-php8.5-zts (default),
SWOOLE_IMAGES="phpswoole/swoole:6.2.3-php8.5" composer integration — NTS.
License
MIT
Related Packages
Provides support of ENUM type for Doctrine2 in Symfony applications.
Extends Doctrine with native PostgreSQL support for arrays, JSONB, ranges, PostG...
Powerful PHP database abstraction layer (DBAL) with many features for database s...