opsource/rabbitmq
A Laravel package that integrates RabbitMQ messaging into PHP applications, providing a simple, configurable API for publishing and consuming messages and connecting AMQP queues to your app's existing workflow and services.
Install the package and let Laravel discover the service provider:
composer require opsource/rabbitmq
php artisan vendor:publish --provider="Opsource\RabbitMQ\RabbitMQServiceProvider"
The publish command copies a config/rabbitmq.php into your app. Expect the usual knobs: host, port, username, password, vhost, default queue, and exchange. Mirror these in .env (RABBITMQ_HOST, RABBITMQ_PORT, etc.) so per-environment brokers don't leak into version control.
Where to look first, in order:
config/rabbitmq.php — it defines the defaults the facade/worker uses.src/ directory — with a 0-star package and no dependents, the source is the documentation. Read the facade class and any registered Artisan commands (rabbitmq:*) before writing code, since the public API surface is small and not widely documented by third parties.First use case: publish a JSON payload from a controller or event listener and consume it with the bundled worker command. A minimal happy path:
use Opsource\RabbitMQ\Facades\RabbitMQ;
RabbitMQ::publish(json_encode([
'event' => 'order.created',
'orderId' => $order->id,
]), 'orders');
Then run the consumer:
php artisan rabbitmq:consume orders
If the command name differs (it likely does), check php artisan list | grep rabbit immediately after install — that's the fastest way to discover the actual worker entry point.
Publish from domain events, not controllers. Bind publishing to Laravel events/listeners so the broker call is decoupled from HTTP request lifecycles. A listener that serializes a small envelope (event, id, payload, occurred_at) keeps the publish call in one place and makes it easy to swap transports later.
Treat the envelope as versioned. Persist a schema_version in the payload. Since you can't rely on a community-driven package for backward-compatible helpers, adding your own versioning now saves migration pain.
Inject the client, don't use the facade everywhere. Bind the package's client into your own interface (MessagePublisher) in a service provider and type-hint that in constructors. Tests then swap in an in-memory fake without needing a live broker. The facade is fine for one-off scripts; interfaces are for domain code.
Long-running worker loop via Supervisor. The consume command is a foreground process. Wrap it in Supervisor with autorestart=true, stopwaitsecs=30, and numprocs scaled per queue. Signal handling matters — SIGTERM should finish in-flight messages and exit cleanly.
Idempotency on consume. Broker delivery is at-least-once by default. Key your handler on a message id (or business key) and short-circuit duplicates with a unique index or Redis SETNX before doing work. This is a consumer-side responsibility — the package won't do it for you.
Dead-letter queues. Configure x-dead-letter-exchange on the queue at declaration time. When the consumer throws, nack (or reject without requeue) and let the broker route to the DLX. Pair this with a small admin command to inspect DLQ depth.
Manual ack, explicit prefetch. Confirm the package exposes basic_ack/nack and a qos/prefetch setting. For anything doing I/O in the handler, set prefetch to 1–10 so backpressure is real. Auto-ack on a slow handler is a silent message-loss bug.
Separate the consumer from the web app if you scale. Two small apps — one publishing, one consuming — avoids the awkwardness of deploying a web fleet that also runs blocking workers. If you stay monolithic, at minimum deploy consumers on a distinct process group.
Bridging to Laravel's queue system. If you want dispatch() semantics, the cleaner route is usually a custom queue connector registered with Queue::extend('rabbitmq', ...), delegating to the package's publish method. That gives you ShouldQueue jobs, retries, and failed-jobs handling for free. The package itself publishes/consumes raw messages — decide early which abstraction you want, because mixing both gets confusing.
Config caching bites hard. php artisan config:cache freezes config/rabbitmq.php. If your consumer container has different env than the web container, cached config hides that. Always config:clear in local dev after editing env, and verify with php artisan tinker --execute="dump(config('rabbitmq'));".
Vhost and permissions. The guest user only works against /. If the package defaults to a named vhost and your RabbitMQ container hasn't created it, connection fails with a confusing ACCESS_REFUSED. Create the vhost, grant configure/write/read, and double-check the connection string includes the vhost in the path (amqp://user:pass@host:5672/vhostname).
Heartbeats and dropped connections. Idle consumers see the broker close the socket after the negotiated heartbeat (~60s default). If the package doesn't reconnect automatically, you'll need to wrap the consume loop in your own reconnect logic or rely on Supervisor restarts. Test by killing the RabbitMQ container mid-consume.
Queue declaration is not free. Redeclaring a queue with different arguments (durable, x-dead-letter-exchange) causes PRECONDITION_FAILED and a hard channel close. Pick durable queues + persistent messages from day one; retrofitting requires deleting the queue.
Memory leaks in long workers. PHP long-runners accumulate memory across messages — especially with Laravel's container resolving new instances. Restart workers on deploy (a queue:restart-style signal or Supervisor stopwaitsecs), and consider exiting after N messages.
Logging and observability. The package likely uses Laravel's logger. Add context (queue, message_id, attempt) at your handler boundary. Because there's essentially no community around this package, you're on the hook for visibility — a structured log line per ack/nack is worth the small cost.
Pin the version. Last release is mid-2024, zero stars, zero dependents. That means no crowd-sourced bug reports. Use an exact constraint ("opsource/rabbitmq": "1.x.x"), read the diff before updating, and consider vendoring the source if you depend on undocumented behavior.
Test against a real broker. For integration tests use testcontainers or a rabbitmq:3-management service in CI. Unit tests should target your publisher interface; don't mock the package's facade — the surface is too thin to mock reliably.
Extension points worth knowing. The service provider's container binding is your override hook: rebind the client to a subclass or decorator for metrics/logging without touching the package. If the package exposes a configurable connection factory, that's also a good place to inject a custom Connection for test doubles or custom retry policies.
How can I help you explore Laravel packages today?