checkthiscloud/phpamqplib-messenger
Symfony Messenger transport for RabbitMQ built on the pure-PHP php-amqplib library instead of the php-amqp C extension. Point a phpamqplib DSN at your broker to create exchanges, queues, and bindings, and gain async consumers, efficient message streaming, and easier PHP upgrades.
Read this first: this is not a Laravel package. checkthiscloud/phpamqplib-messenger is a Symfony bundle (Jwage\PhpAmqpLibMessengerBundle\PhpAmqpLibMessengerBundle) that registers a Symfony Messenger transport. There is no config/queue.php driver, no php artisan queue:work integration, no Horizon support, and no failed_jobs table. If your mental model is QUEUE_CONNECTION=redis, reset it. What you actually get is a TransportInterface/ReceiverInterface implementation that speaks AMQP over pure PHP via php-amqplib, plus a Symfony DI extension that wires it up from a DSN.
Your first decision is therefore whether you want to bring symfony/messenger + symfony/dependency-injection into a Laravel app at all. In most cases the honest answer is: only if you have a hard blocker on ext-amqp (Alpine/Docker images you don't control, shared hosting, a PHP upgrade that breaks the C extension, or a PHP-FPM/host where you can't compile extensions) and you need true push-based consuming rather than polling.
Minimal steps to get a real signal in ~30 minutes:
composer require checkthiscloud/phpamqplib-messenger in a scratch Symfony 6.4 or 7.x skeleton — not in your Laravel app yet.ext-sockets is present (php -m | grep sockets); v0.11.0 added it as a hard requirement.config/bundles.php and add the smallest possible transport:# config/packages/messenger.yaml
framework:
messenger:
transports:
orders:
dsn: 'phpamqplib://guest:guest@localhost:5672/myvhost/orders'
bin/console messenger:consume orders -v. This proves the whole DSN → exchange → queue → bind → publish → ack path before you write any Laravel glue.Where to look next: docs/index.md in the repo is the only substantive documentation; the README covers the "why" and the reliability model but assumes Symfony knowledge. The roadmap states the intent to land in Symfony core as symfony/phpamqplib-messenger for 7.4 LTS — treat that as the strategic reason to care, not a shipping guarantee.
First use case to target: an outbound-only producer (Laravel dispatches to RabbitMQ, an existing non-PHP consumer processes). That avoids reimplementing worker lifecycle in Laravel on day one and is where the pure-PHP argument pays off with the least risk.
Know which layer you're writing. In Symfony you write almost nothing: route messages to the transport, tag handlers, run messenger:consume. In Laravel you own the bridge, and there are only two sane shapes.
Pattern A — Symfony Messenger bus inside Laravel (producer-only). Bind MessageBusInterface in a service provider against a hand-built Symfony Messenger with this transport. Your Laravel jobs/mailable-style classes become Envelope-wrapped messages.
// AppServiceProvider::register()
$this->app->singleton(MessageBusInterface::class, function () {
$transport = /* TransportFactory built from the phpamqplib:// DSN */;
return new MessageBus([new SendersLocator([YourMessage::class => [$transport]], $container)]);
});
Dispatch: $bus->dispatch(new SendInvoice($id)). This is the lowest-friction path and degrades gracefully — if you later move to Symfony, the message classes travel with you.
Pattern B — custom Laravel queue driver. Implement Illuminate\Contracts\Queue\Queue and have push() translate a Illuminate\Queue\Jobs\Job payload into a Messenger envelope. More idiomatic (dispatch(new Job) just works) but you reimplement serialization, attempts, backoff, and failure handling that Messenger already solved. Only do this if several teams are already writing Laravel jobs and you can't change call sites.
Pattern C — consumer side, when you must. Mirror messenger:consume in an Artisan command: build the transport, call get() in a loop or getReceiver()->receive(), hand the envelope to a handler locator, then ack()/nack(). Register it under Supervisor exactly like queue:work. Because php-amqplib is genuinely async, blocking on the socket rather than polling means idle workers cost far less — this is the concrete win over symfony/amqp-messenger.
Let the DSN do the declaring. phpamqplib://user:pass@host:5672/vhost/orders creates exchange orders and a queue orders bound to it in myvhost. Use this for the first iteration so you have zero config drift; only move to explicit exchange/queue options (type, durability, arguments, DLX, prefetch) once you need non-default topology. Keep the DSN in .env per environment — vhost differences between local/staging/prod are the #1 source of "works on my machine".
Treat idempotency as a hard requirement, not a tip. Confirms are on by default and publishes are retried on connection exceptions, which explicitly means at-least-once with possible duplicates. Design handler-side dedupe from the first commit: a unique constraint on a business key, or an INSERT ... ON CONFLICT DO NOTHING into a processed-messages table keyed by a message ID you put in the envelope headers.
Integrate via events and health checks, not via queue:work internals. Since you lose queue:failed, queue:retry, and Horizon metrics, wire your own: a dead-letter queue plus an operator command to replay it, and a php artisan rabbitmq:health that opens the connection and asserts the queue exists so your deploy pipeline catches misconfiguration before traffic does.
Test at the envelope boundary. Never hit a real broker in unit tests; use Messenger's in-memory transport or an Envelope-in/Envelope-out assertion on your bridge, and reserve real-broker tests for a single integration test per transport.
Jwage\PhpAmqpLibMessengerBundle but the Composer name is checkthiscloud/phpamqplib-messenger. Autocomplete, Stack Overflow, and IDE suggestions will all point at the wrong name. Pin the version explicitly in composer.json; don't float ^0.x on a package with one maintainer, zero dependents, and 0.x semantics.0.11.x is pre-1.0 and moving. Release notes are thin ("Hotfix", "Remove unnecessary ext", "Add ext-sockets dependency") and one release date is in the future relative to normal timelines. Read the diff, not the changelog, before every bump — especially anything touching fetchSize, which the 8.1 receiver contract now passes in and which is your prefetch/latency tuning lever.app() and Laravel facades will not resolve inside Symfony-wired services, and Symfony's container won't see your Laravel bindings. Keep the bridge thin: construct Symfony objects in a Laravel provider, pass plain values across the boundary, and never let a handler depend on Auth::user().PRECONDITION_FAILED - inequivalent arg 'x' (406) is the classic first failure. It means the exchange or queue already exists with different arguments than the bundle is declaring. You cannot fix it in code — delete and redeclare in the management UI, or align your config. This is why you should not point a dev branch at a queue shared with production.ACCESS_REFUSED almost always means the user lacks configure/write on that vhost, or the vhost was never created. Check rabbitmqctl list_vhosts / list_permissions before debugging the DSN parser.pcntl discipline. The bundle's stated advantage is safe shutdown via signals so a handler isn't killed mid-message. In a custom Artisan consumer, install SIGTERM/SIGINT handlers that set a "stop after current message" flag, and add --time-limit/--memory-limit equivalents — a long-lived AMQP connection plus Doctrine keeps growing.symfony/messenger and symfony/dependency-injection at 6.4+; any Laravel package in your tree pinning older Symfony components will produce an unsatisfiable set. Run composer why symfony/dependency-injection before you commit to the approach.php-amqplib's debug/connection logging at the factory level to see frames, and cross-check the RabbitMQ management UI — if the exchange appears but the queue doesn't, your consumer never started; if bindings are missing, your config was ignored. The bundle leaves no Laravel-visible trace, so the management UI is your source of truth.phpamqplib DSN scheme. If you need custom behaviour (middleware, custom envelope stamps, connection tuning), decorate or re-register that factory rather than subclassing the transport, and keep the DSN scheme stable so config stays portable to the eventual symfony/phpamqplib-messenger.ext-amqp "because it's simpler."How can I help you explore Laravel packages today?