Symfony PostgreSQL Messenger Bridge
A Symfony Messenger transport on the native PostgreSQL library of Flow PHP. It replaces symfony/doctrine-messenger and does not need Doctrine DBAL. Messages are stored directly in PostgreSQL with the query builder and client of Flow.
Installation
composer require flow-php/symfony-postgresql-messenger-bridge:~0.47.0
Use this package together with the Symfony PostgreSQL Bundle. The bundle registers the transport factory and the catalog provider automatically.
How It Works
The bridge has a Symfony Messenger transport. It stores messages in a PostgreSQL table with the native Client of Flow. Doctrine DBAL is not used.
- A send inserts a row with
body,headers,queue_name,created_at, andavailable_at. - A receive uses
SELECT ... FOR UPDATE SKIP LOCKEDinside a transaction, so concurrent consumers are safe. - Delayed messages move
available_atby theDelayStampvalue (in milliseconds). - Timed-out messages are delivered again automatically, after
redeliver_timeout. - Keepalive refreshes
delivered_at, so that long-running handlers do not cause a second delivery.
Configuration
1. Enable Messenger
In your flow_postgresql configuration, enable messenger at the root level:
# config/packages/flow_postgresql.yaml
flow_postgresql:
connections:
default:
dsn: '%env(DATABASE_URL)%'
messenger:
enabled: true
table_name: messenger_messages # default
schema: public # default
This registers a MessengerCatalogProvider and a transport factory that resolves each configured connection by name. Schema diffs and migrations then include the messenger_messages table automatically.
2. Configure the Messenger Transport
# config/packages/messenger.yaml
framework:
messenger:
transports:
async:
dsn: 'flow-pgsql://default'
options:
queue_name: default # default
redeliver_timeout: 3600 # seconds, default
routing:
App\Message\MyMessage: async
The DSN format is flow-pgsql://<connection_name>. <connection_name> is the name of a connection under flow_postgresql.connections. The flow-pgsql:// and flow-postgresql:// schemes are both supported.
3. Create the Table via Migrations
The catalog provider is registered automatically, so the messenger table is in the migration diffs:
php bin/console flow:migrations:diff
php bin/console flow:migrations:migrate
This is the recommended approach. The bridge does not implement SetupableTransportInterface, so messenger:setup-transports is not supported. Use migrations for schema management.
DSN Options
You can set options in the DSN query string or in the options array of the transport configuration. The options array wins over DSN query parameters.
| Option | Default | Description |
|---|---|---|
queue_name |
default |
Queue name for message routing |
table_name |
messenger_messages |
Table that stores messages (must match the flow_postgresql.messenger configuration) |
schema |
public |
Schema that owns the table (must match the flow_postgresql.messenger configuration) |
redeliver_timeout |
3600 |
Seconds before a delivered but unacknowledged message can be delivered again |
Example with DSN options:
framework:
messenger:
transports:
high_priority:
dsn: 'flow-pgsql://default?queue_name=high&redeliver_timeout=1800'
Multiple Queues
Multiple transports can share the same table with different queue names:
framework:
messenger:
transports:
async:
dsn: 'flow-pgsql://default'
options:
queue_name: default
high_priority:
dsn: 'flow-pgsql://default'
options:
queue_name: high
routing:
App\Message\ImportantMessage: high_priority
App\Message\BackgroundTask: async
Table Schema
The messenger table has this structure:
| Column | Type | Description |
|---|---|---|
id |
bigint GENERATED ALWAYS AS IDENTITY |
Primary key |
body |
text |
Serialized message body |
headers |
text |
JSON-encoded message headers (stamps, type info) |
queue_name |
varchar(190) |
Queue name for message routing |
created_at |
timestamptz |
Time of the dispatch |
available_at |
timestamptz |
Time from which consumers can take the message |
delivered_at |
timestamptz |
Time of the pickup by a worker (null = not delivered yet) |
The bridge creates three indexes: on queue_name, available_at, and delivered_at.
Found a typo or an outdated section? Edit this page on GitHub