Setup
Install the package:application.yml:
application.yml
In yourapplication.yml you can configure datasources with a doctrine block under each entry. The example below defines two datasources named defaultdb (primary) and admindb:
Autowired bean naming
ORM and DBAL beans are created automatically and can be autowired by name. The suffix pattern is:ORM EntityManager
ORM Transaction Managers
DBAL Connection
DBAL Transaction Managers
Swoole: one fresh EntityManager per request (automatic)
Think of theEntityManager as a notebook. It remembers every database row
your code has touched, so it does not have to ask the database twice. That
is great inside one request — and dangerous on a Swoole server, where one
worker handles hundreds of requests one after another. Without protection,
request number 2 would read request number 1’s old notes instead of fresh
data from the database. Worse, two requests running at the same time would
scribble in the same notebook and corrupt each other’s unflushed changes.
This module fixes that for you, automatically. Under Swoole, every request
(or job, or message) silently gets its own blank notebook with its own
database connection. When the request finishes, its notebook is thrown away.
You change nothing in your code:
instanceof EntityManager checks, type-hints, and both
transaction styles (#[Transactional] and EmTransactionManager) all work
unchanged. Code paths that never touch the database allocate nothing, and
short-lived CLI scripts behave exactly as before.
Four rules to stay out of trouble:
-
Always use the injected
EntityManageritself. Do NOT save things it gives you (getRepository()results, query builders) into a property and reuse them later — those belong to one request’s notebook, which gets thrown away. Fetch them fresh from theEntityManagerevery time: - Keep event listeners stateless. One shared event dispatcher serves all requests, so a listener that stores things in its own properties will mix up requests. Compute from the event, store nothing.
-
Long-running loops are the one exception. A daemon or scheduler that
processes 10,000 items inside a single never-ending task reuses one
notebook for all 10,000 items — and it keeps growing. Wrap each round so
it gets a blank notebook:
You do NOT need this helper for normal controllers, services, SQS/Kafka consumers, or scheduled jobs. Those already get a fresh notebook per run. Only loops that never end need it.
-
One database connection per concurrent request, with a safety cap.
Every request that touches the database opens its own real database
connection, so two requests can never share (and corrupt) one. Requests
that never touch the database open none, and every connection is closed
when its request finishes. The log line
Doctrine open DB connections: {...}shows you the live count per pool — watch it after deploys. Each pool is capped at 50 open connections. When all 50 are busy, the next request waits up to 5 seconds for one to free up; if none does, it fails fast with a clearPoolExhaustedExceptioninstead of silently sharing (sharing would bring back the corruption bug). Tune both inapplication.yml:A single datasource can override both caps in its ownconnectionblock (the override wins over the global defaults):How to size the cap: add up every pool (one EntityManager pool plus one DBAL pool per datasource, per tenant — using each datasource’s override where one is set), multiply by your Swoole worker count, and keep the total below the database’smax_connections(MySQL defaults to 151, Postgres to 100). When in doubt, keep 50 and raise the database limit first — a loudPoolExhaustedExceptiontelling you to raise the cap is always better than a cryptic “too many connections” from the database at 3 AM. If the coroutine machinery itself fails, the failure is logged and the request degrades instead of crashing — but pool exhaustion stays loud: it still throwsPoolExhaustedExceptionrather than silently sharing a connection.
How to use Transactions
Declarative Transactions (AOP)
Executing something under ORM/DBAL transaction is easy by just using#[Transactional]:
Programmatic Transactions
For fine-grained control, useEmTransactionManager (ORM) or DbalTransactionManager (DBAL) with the getTransaction() / commit() / rollback() pattern.
ORM Example: UserService with EmTransactionManager
DBAL Example: UserDbalService with DbalTransactionManager
Multi-Tenant Support
Winter Doctrine provides native support for multi-tenancy viaMultiTenantManager and TenantDataSourceProvider (from Winter Boot), allowing per-tenant EntityManager, Connection, and transaction manager instances.
Configuration via application.yml
You can configure multi-tenant data sources directly in yourapplication.yml by registering a provider class:
MultiTenantManager bean named <name>-manager (for example, tenantdb-manager) in the application context.
Step 1: Implement TenantDataSourceProvider
Create a#[Configuration] class that returns your implementation of TenantDataSourceProvider:
Step 2: Use in Business Classes
With Multiple MultiTenantManagers
If you have multiple multi-tenant data sources:API Reference
MultiTenantManager
TheMultiTenantManager class manages tenant-specific Doctrine connections and entity managers.
getEntityManager
Returns a cached per-tenantEntityManager.
getConnection
Returns a cached per-tenantConnection.
getEmTransactionManager
Returns a cached per-tenantEmTransactionManager.
getDbalTransactionManager
Returns a cached per-tenantDbalTransactionManager.