nti/rabbitmq-bundle
nti/rabbitmq-bundle integrates RabbitMQ messaging into Symfony applications, providing services and configuration for publishing and consuming AMQP messages. It simplifies connection setup, queue and exchange declarations, and producer/consumer wiring so PHP apps can add reliable asynchronous messag...
This is a Symfony bundle, not a Laravel package — there is no service provider, no auto-discovery, no config/rabbitmq.php merge. From a Laravel developer's perspective the realistic first step is a decision:
vladimir-yuldashev/laravel-queue-rabbitmq (wraps php-amqplib, plugs into Queue::push, workers, failed jobs). It gives you 90% of what this bundle is for, without Symfony plumbing.php-amqplib directly from a Laravel service class. That is usually cleaner than importing a Symfony bundle into a modern Laravel app.Where to look first (if you do pursue it):
nti_rabbit_mq.connections / producers / consumers). Treat it as a cheat-sheet: each producer is an exchange + routing key + AMQPMessage flags; each consumer is a queue + callback + qos settings.connections node — since v1.15.1 it accepts multiple hosts per connection, which is worth copying into your own connection factory design.Producer / Consumer / Connection classes — this is thin sugar over AMQPLib\Channel. If you're not using the bundle, these are the ~300 lines you reimplement.php-amqplib AMQPStreamConnection API — this is what you actually program against.First use case (framework-agnostic): publish a JSON message to a direct exchange from a Laravel service, and consume it in an Artisan command. Declare the exchange/queue/binding once at boot (idempotent), then publish with content_type: application/json and an explicit delivery_mode = 2.
Producer as a singleton service. Open one connection/channel per process and reuse it. Publish through a small adapter (e.g. App\Messaging\Publisher) so producers are swappable and testable. Always set content_type and delivery_mode (persistent) explicitly.
Multiple-host connections (new in v1.15.1). The bundle now lets a single connection definition hold a list of hosts. If you mirror that in Laravel config, keep the same idea: pass an array of host/port entries to AMQPStreamConnection (php-amqplib already accepts an array and rotates/fails over at connect time) and don't cache the choice — let the client pick. This buys resilience against a broker node being down at connect time; it does not give you mid-operation failover.
$connection = new AMQPStreamConnection(
['rabbitmq-1', 'rabbitmq-2', 'rabbitmq-3'], // multiple hosts
5672, $user, $pass, $vhost
);
Consumer as an Artisan command. The canonical loop:
$channel->basic_qos(0, 10, false); // prefetch
$channel->basic_consume($queue, '', false, false, false, false, $handler);
while ($channel->is_consuming()) {
$channel->wait(); // blocks; exits on signal or channel error
}
Wrap the handler with try/catch and choose ack (success), nack+requeue (transient), or nack→DLX (poison message). Run under Supervisor/systemd with --max-messages or a max-lifetime so memory doesn't grow unbounded.
Topology as code. Declare exchange_declare, queue_declare, queue_bind on startup, with identical arguments everywhere. Use passive checks or a migration command for production; declare in a dedicated rabbitmq:setup Artisan command rather than on every publish.
Work queue vs pub/sub.
direct/default exchange, multiple worker processes on one durable queue.fanout exchange, per-service queues each bound to it — each service gets its own copy.topic exchange with order.*.created style keys for selective consumption.Retries / delays. Either (a) per-queue x-dead-letter-exchange + x-message-ttl retry queues, or (b) the rabbitmq_delayed_message_exchange plugin with an x-delayed-type exchange. Keep retry count in a header, not the payload.
RPC / request-reply. Set reply_to and correlation_id; consumer replies to the callback queue. Rarely worth it — prefer async events, but it's a real pattern when you need a synchronous response.
Bridging to Laravel. Publish from domain events (Event::listen) or model observers, not from controllers. Consume into jobs or directly into service classes. Keep the AMQP layer at the boundary; don't leak AMQPMessage into your domain.
Testing. Point at a disposable broker (Testcontainers / Docker) or a local RabbitMQ in CI. Unit-test the handler in isolation by calling it with a fake AMQPMessage and asserting ack/nack calls on a mocked channel.
rabbitmq.xml) shows there is some activity, but nothing here changes the fundamentals: it remains a Symfony bundle with a tiny user base, and the feature set you consume is narrow. Pin the version, vendor/fork if you depend on it, and keep the migration-to-raw-php-amqplib path open — the API surface you actually use is small.AMQPConnectionClosedException / AMQPIOException, back off, reconnect, and re-declare topology.rabbitmq.xml classname fix is a reminder that Symfony service wiring changes can silently break bundle instantiation. If you vendor this bundle, add a smoke test that boots the container and asserts the producer/consumer services resolve — that's exactly the class of bug this release fixes.$channel->wait() blocks; heartbeats and read_write_timeout must be configured together. A too-short read_write_timeout with a long-lived idle connection causes "connection reset by peer" loops.PRECONDITION_FAILED on declare means the existing queue/exchange has different arguments (durable, auto_delete, x-message-ttl, x-dead-letter-exchange) than your declaration. Make declarations match production before touching them, or use passive mode.Producer across Octane workers naively — reset per request/worker.SIGTERM/SIGINT, set a flag, let the in-flight message finish and ack, then close the channel. Otherwise you get spurious redeliveries and duplicate side effects on deploy.application/json and a consumer expects serialized PHP (or vice versa), you'll get silent garbage. Standardize on JSON + UTF-8 and validate the envelope on consume.x-message-ttl on a normal queue gives you scheduled delivery.rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers, the management UI for bindings, and rabbitmq_tracing when routing keys vanish. Most "message lost" bugs are unbound queues or a mismatched routing key.heartbeat, connection_timeout, read_write_timeout, channel_rpc_timeout live under connection options; consumers carry separate qos settings; producers carry exchange + routing key. With v1.15.1 the host entry can now be a list — check how your config layer normalizes it before assuming a single host string. If you translate this to Laravel config, keep the same nesting or you'll misplace timeouts.x-queue-type arguments and (for quorum) durable queues — they will not work with auto_delete or exclusive consumers. Decide topology up front; converting a live queue is a drain-and-recreate.How can I help you explore Laravel packages today?