simple-bus/bernard-bundle-bridge
Bridge between SimpleBus and BernardBundle that lets Symfony applications dispatch commands and events through Bernard queues. It wires message buses to asynchronous transports so handlers can run in background workers, with configuration for routing messages to queues.
This package is thin glue: it connects SimpleBus/AsynchronousBundle (which decides which messages should be published asynchronously and how to serialize them) to BernardBundle (which decides where they are stored/transported — Doctrine, SQS, IronMQ, Redis, MongoDB, etc.). It is not a message bus and not a queue; you need both sides already installed and configured.
Minimal bootstrap on a Symfony 2/3 application:
composer require simple-bus/bernard-bundle-bridge
// app/AppKernel.php
new Bernard\BernardBundle\BernardBundle(),
new SimpleBus\AsynchronousBundle\SimpleBusAsynchronousBundle(),
new SimpleBus\BernardBundleBridge\BernardBundleBridge(), // verify the exact class name in src/
# app/config/config.yml
bernard:
driver: doctrine # or sqs, redis, ironmq, mongodb, ...
doctrine: { connection: default }
simple_bus_bernard_bridge:
queue_name: simple_bus
Then mark commands/events as asynchronous using AsynchronousBundle's configuration (its documented simple_bus_asynchronous block lists the command/event classes that should be published rather than handled inline). Anything routed as "async" is handed to the bridge, serialized by SimpleBus, and pushed to the Bernard queue you named.
First use case to prove the wiring: a single slow command (send a newsletter, generate a report). Dispatch it through the command bus from a controller. It should return immediately, a row/message should appear in the Bernard transport, and the message should be handled by:
app/console bernard:consume simple_bus
Where to look first, in this order:
DependencyInjection/Configuration.php — the only authoritative source for config keys (queue_name, serializer service, etc.).Resources/config/services.xml — service IDs, the bernard.consumer tag, and how the producer decorates SimpleBus's publisher. This tells you what to inject and what to override.src/ — you will find a producer (a SimpleBus Publisher implementation calling Bernard's Producer), a consumer (a Bernard Consumer implementation calling SimpleBus's message handler), and a queue-name resolver.A caveat before adopting: the package is archived, last released 2017, effectively unmaintained, with 2 stars. Use it only to keep an existing Symfony 2/3 application running; for anything new, treat it as a migration target (Symfony Messenger is the modern equivalent).
One queue, or route by type. The default configuration uses a single fixed queue (a fixed queue-name resolver). That is fine for a small app: every async command and event lands in simple_bus and one worker pool handles them. When a slow event class starts starving your commands, replace the fixed resolver with a custom implementation that inspects the message and returns a queue name per type, then run one bernard:consume <queue> process per queue. This is the primary extension point.
Producer side: publish through the bus, not the queue. Do not inject Bernard's producer into controllers. Dispatch through the normal command/event bus and let AsynchronousBundle + the bridge decide whether it is handled inline or published. Keeps sync/async a configuration concern and makes it trivial to flip a message back to synchronous for debugging. If you must publish manually, inject SimpleBus's asynchronous publisher service (find its ID in services.xml) so serialization and queue resolution stay consistent.
Consumer side: run it as a long-lived daemon. bernard:consume <queue> is a worker. Run it under supervisor/systemd with --no-debug in production, and size your pool to the transport. For Doctrine transports, that means multiple bernard:consume processes against the same table with an appropriate visibility/lock timeout; for SQS/Redis, watch prefetch and visibility timeouts. Always bound the work: a handler that takes longer than the visibility timeout will be redelivered and processed twice.
Keep messages small and self-describing. The message is serialized, stored, possibly retried days later, and deserialized by a different process running a possibly different deploy. Send scalars and IDs; re-load entities from the database inside the handler. Add an explicit idempotency key to messages whose handlers touch money, email, or external APIs, and enforce it with a unique constraint or a processed-messages table — you are on an at-least-once transport.
Custom serializer / metadata. SimpleBus's serialization layer needs class metadata (JMS or Symfony serializer with the appropriate mapping). If you send domain objects with nested graphs, you will fight the serializer; prefer DTOs. If you need a different format, implement the bridge's serializer interface and point the bundle at your service. Remember both ends must agree: the producer serializes, the consumer deserializes, so change both in the same deploy.
Testing pattern. Unit-test handlers directly — they are plain callables and should not depend on the bus or the queue. For integration, swap the asynchronous publisher for SimpleBus's synchronous/in-memory implementation (or configure the message as sync) so the test exercises the handler without a worker. For a full round-trip, run Bernard with an in-memory or filesystem transport and consume from the test itself.
Migration sketch. Mapping the concepts to Symfony Messenger clarifies what you actually rely on: the bridge producer becomes a bus dispatch with a routing rule (routing: { 'App\Message\Foo': async }), bernard:consume <queue> becomes messenger:consume async, the queue-name resolver becomes the transport/routing map, and the serializer becomes Messenger's serializer. Handlers move from SimpleBus handler registration to #[AsMessageHandler]/messenger.message_handler tags.
simple_bus_bernard_bridge.queue_name but still run the old worker command, messages pile up in a queue nobody consumes. Dump the container (debug:container / container:debug) and confirm which bernard.consumer tagged services exist and for which queue names.private properties and getters. Depending on the serializer in use, private state may not round-trip without mapping/accessor configuration. Verify by asserting serialize(deserialize(x)) == x for each message class in a test — this catches most production surprises cheaply.messenger:failed equivalent here — you build it.How can I help you explore Laravel packages today?