Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rabbitmq Bundle Bridge Laravel Package

simple-bus/rabbitmq-bundle-bridge

Symfony bundle bridge that connects SimpleBus to RabbitMQ, making it easier to wire message buses to RabbitMQ queues for asynchronous command and event handling with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is glue, not a framework. It wires the Publisher/Consumer abstractions of SimpleBus/AsynchronousBundle to the producers/consumers of OldSoundRabbitMQBundle. You need all three packages plus simple-bus/message-bus and simple-bus/serialization; the bridge itself is only a handful of classes.

1. Check installability before you plan anything — this is the step that changes most. The latest tag is v6.2.0, whose only change is dropping support for Symfony ^5.2; the package now expects Symfony ^5.3 (and the 6.x line the 6.x tags target). That is a constraint bump, not a feature release, so the practical rule is unchanged: verify against your lockfile before writing any config.

composer require simple-bus/rabbitmq-bundle-bridge --dry-run
composer why-not simple-bus/rabbitmq-bundle-bridge

Two outcomes matter:

  • On Symfony ^5.2: v6.2.0 is not installable for you. You are stuck on the previous 6.1.x tag — pin it explicitly ("simple-bus/rabbitmq-bundle-bridge": "6.1.*"). Note this is a minor release that removes a supported constraint, i.e. a BC break delivered as a non-major bump — do not let ^6.0 float blindly.
  • On Symfony ^5.3 or 6.x: the bridge still installs, but that only proves the metadata resolves. Both upstreams have moved on (OldSoundRabbitMQBundle was renamed to php-amqplib/rabbitmq-bundle; SimpleBus is effectively frozen). If Composer refuses, stop — this is a codebase-compatibility decision, not a config problem.

2. Register bundles (in order matters only for parameter resolution, not functionally):

// config/bundles.php
OldSound\RabbitMqBundle\OldSoundRabbitMqBundle::class => ['all' => true],
SimpleBus\AsynchronousBundle\SimpleBusAsynchronousBundle::class => ['all' => true],
SimpleBus\RabbitMQBundleBridgeBundle\SimpleBusRabbitMQBundleBridgeBundle::class => ['all' => true],

3. Read the source before you wire it. The repo is ~2 production classes (RabbitMQProducer, RabbitMQConsumer) plus a DI extension. Because there are effectively zero dependents in the wild, and the 6.x changelog is constraint bumps only (no docs refresh, no new extension points), treat the source and its Tests/ as the authoritative docs and confirm service ids with:

bin/console debug:container --show-private | grep -i rabbit_mq

4. First use case — publish a command asynchronously and consume it in a worker. Declare the transport in old_sound_rabbit_mq, point a producer at the bridge's publisher, and point the OldSound consumer's callback at the bridge's consumer (it implements ConsumerInterface, so it plugs in directly):

# config/packages/old_sound_rabbit_mq.yaml
old_sound_rabbit_mq:
    connections:
        default:
            host: '%env(RABBITMQ_HOST)%'
            user: '%env(RABBITMQ_USER)%'
            password: '%env(RABBITMQ_PASSWORD)%'
            vhost: '/'
    producers:
        order_commands:
            connection: default
            exchange_options: { name: order_commands, type: direct }
    consumers:
        order_commands:
            connection: default
            exchange_options: { name: order_commands, type: direct }
            queue_options: { name: order_commands }
            qos_options: { prefetch_size: 0, prefetch_count: 10, global: false }
            callback: simple_bus_rabbit_mq_bundle_bridge.consumer

Then swap the Publisher injected into your async middleware for the bridge's RabbitMQ publisher, and run a worker:

bin/console rabbitmq:consumer order_commands -m 100 -vvv

If a message appears in the management UI but nothing happens in the worker, the problem is almost always the callback service id or the bus id the consumer dispatches to — log both at boot.

Implementation Patterns

Two buses, two transports. The healthy pattern is one async command bus (direct exchange, one queue, one worker pool) and one async event bus (fanout exchange, one queue per subscriber group). Don't share a queue between commands and events — a poison command will block your event stream.

Producer side — keep the async bus a drop-in. SimpleBus's async middleware replaces the synchronous bus transparently, so controllers keep dispatching commands and never touch AMQP:

final class PlaceOrderHandler
{
    public function __construct(private CommandBus $commandBus) {}

    public function __invoke(PlaceOrder $command): void
    {
        // goes through the async middleware -> bridge publisher -> RabbitMQ
        $this->commandBus->handle(new ReserveStock($command->orderId()));
    }
}

Consumer side — one worker process, one queue, one bus. The bridge consumer receives an AMQPMessage, deserializes it via the SimpleBus serializer/deserializer, and hands the message to the configured message bus. Keep the dispatch path pure: no HTTP calls, no long transactions inside the handler; the handler is your retry boundary.

Wrap the handler in idempotency. AMQP is at-least-once. Track processed message ids (a processed_messages table with a unique key, insert-on-conflict) rather than trying to configure exactly-once.

Run workers under a supervisor with bounded message counts. -m 100 (or a memory guard) forces the process to recycle and papers over leaks in long-lived PHP — necessary because the bundle gives you no lifecycle hooks of its own.

[program:order_commands]
command=php /app/bin/console rabbitmq:consumer order_commands -m 100 --no-debug
autorestart=true
numprocs=4
stopsignal=TERM

Split queues by failure behavior. Configure a dead-letter exchange on queue_options.arguments for anything that shouldn't be retried forever; the bridge has no DLX logic, that is purely transport config.

Testing — replace the publisher, not the transport. In functional tests, bind Publisher to an in-memory implementation that records messages, and assert on the recorded set. For consumer tests, instantiate RabbitMQConsumer directly with a stub AMQPMessage and a real bus with spy handlers; you avoid needing a broker in CI entirely.

Gotchas and Tips

  • The package is maintenance-only, and "maintenance" means constraint bumps. v6.2.0 contains exactly one line of intent — drop Symfony ^5.2 — with no new features, no deprecations, no docs. Infer from that: no DLX helper, no retry API, no serializer contract work is coming. Pin versions in composer.json and put a comment explaining why.
  • A dropped constraint shipped in a minor tag. If you were relying on ^5.2, v6.2.0 breaks you on a non-major bump. Pin 6.1.* (or exact-pin 6.2.0 if you're current) and treat this changelog as a list of platform requirements, not of behaviour changes — behaviour is unchanged, which is both reassuring and a warning that nothing is being fixed.
  • OldSoundRabbitMQBundle naming matters. If you are on the renamed fork, service ids (old_sound_rabbit_mq.*) and class namespaces (OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface) differ from what the bridge imports — you will need a shim or a fork.
  • Consumer ack semantics are the top foot-gun. Returning false from ConsumerInterface::execute() causes requeue in OldSound. An exception during deserialization (renamed class, schema change, bad JSON) will therefore loop forever. Catch Throwable around deserialization, log the raw body, and return true (ack) so a human can replay from the DLX.
  • Serialized envelopes embed the fully-qualified class name. Renaming or moving a command/event class silently breaks every in-flight message. Treat message class names as a public API; version payloads explicitly and keep a Legacy* shim if you must move them.
  • Serializer compatibility across deploys. A rolling deploy where producer and consumer run different serializer versions is the classic source of "works in staging" failures. Deploy consumers first, or use a schema-tolerant serializer.
  • Recursive publish loops. An async handler that dispatches the same command type again (e.g. via a retry strategy) will hammer the queue. Add a retry counter in the payload and refuse beyond N, rather than relying on requeue.
  • Prefetch and memory. qos_options.prefetch_count is per-channel; the default of 0 will happily give a worker thousands of unacked messages and blow memory during -m 100 recycling. Start at 10.
  • Upgrade the bridge and Symfony together, never separately. The 6.2.0 constraint change means framework and bridge versions are now coupled by Composer metadata you don't control. Bump Symfony and this package in the same PR, and re-run the dry-run and why-not commands afterwards.
  • Debugging checklist: management UI for queue depth and unacked counts → rabbitmq:consumer -vvv for the callback and bus id → debug:container for the publisher/consumer service ids → check the message's content_type and body (empty body means the producer was handed the wrong object).
  • Extension points are thin on purpose. To customize routing, wrap rather than patch: decorate the bridge's publisher to add headers or to route by message type, and implement your own ConsumerInterface that delegates to the bridge consumer after adding tracing/logging. Since no new extension points arrived in 6.2.0, assume you will own any such code indefinitely.
  • Don't use this for a new project. If you are starting greenfield, this package has no advantage over Symfony Messenger's AMQP transport, which has maintenance, retry/DLX support, and a serialization contract. The bridge only makes sense when you already have a SimpleBus bus graph with async middleware in place.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager