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

Burrow Laravel Package

evaneos/burrow

Evaneos Burrow is an AMQP library for PHP that works with both php-amqplib and the pecl amqp C extension. Declare exchanges, bind queues, publish async messages, and build consumer daemons.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you write a line of code, do a constraint check. Burrow's last release is v4.6.0 (2021-10-05). Run composer require evaneos/burrow and watch what Composer says about PHP and php-amqplib. If it resolves cleanly on your runtime (PHP 8.2/8.3, php-amqplib v3), proceed. If it demands --ignore-platform-reqs, stop and decide now whether you're forking — do not adopt a package that needs platform-req suppression on day one.

composer require evaneos/burrow
composer show evaneos/burrow        # confirm the installed version and its requires

Bring up a broker locally (the README's suggested image is enough):

docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management
# management UI at http://localhost:15672 — you will live in this UI while debugging

Where to look first, in order:

  1. src/Driver/DriverFactory.php — this is the whole package in one file. Read the config keys it accepts (host/port/user/pwd, plus the driver-selection key and SSL options) and confirm how it chooses between php-amqplib and ext-amqp. Everything else is built on the driver it returns.
  2. examples/ — the README's snippets are lifted from here; the examples directory is more complete than the README and is the fastest way to see a working publisher + consumer pair.
  3. src/Handler/HandlerBuilder.php and the evaneos/daemon package (QueueHandlingDaemon, Worker) — this is where the consumption semantics actually live. Burrow delegates the run loop; most surprises come from evaneos/daemon, not Burrow.
  4. src/Event/ and src/Metric/ — small, legible, and the main extension points you'll actually use.

First use case — pick the cheapest one. Don't start with a consumer. Start with a one-off topology script plus a publish, because it validates connection, credentials, exchange/queue declaration, and message serialization in about twenty lines:

$driver = DriverFactory::getDriver([
    'host' => 'localhost', 'port' => '5672',
    'user' => 'guest',  'pwd'  => 'guest',
]);

$driver->declareExchange('orders');
$driver->declareAndBindQueue('orders', 'orders.notifications');

$publisher = new AsyncPublisher($driver, 'orders');
$publisher->publish(json_encode(['id' => 42]), 'orders.created', ['content_type' => 'application/json']);

Check the queue depth and message body in the RabbitMQ management UI. If that works, the rest of the package will behave.

There is no Laravel integration. No service provider, no publishable config, no queue driver, no Horizon bridge. Your first real task is to create config/burrow.php and a small BurrowServiceProvider that binds a driver as a singleton and exposes a publisher behind your own interface. Do that before writing feature code, or DriverFactory calls will metastasize across the codebase.

Implementation Patterns

Bind the driver once, lazily, and never expose it. Under php-fpm you get a connection per request, which is fine but wasteful; under a long-running worker you want one connection for the process. A singleton binding gives you both, and lets you swap a fake in tests:

// AppServiceProvider::register()
$this->app->singleton(DriverInterface::class, fn () => DriverFactory::getDriver(config('burrow.connection')));
$this->app->singleton(MessagePublisher::class, fn ($app) => new BurrowPublisher(
    new AsyncPublisher($app->make(DriverInterface::class), config('burrow.exchange'))
));

Define MessagePublisher::publish(string $event, array $payload): void yourself and treat Burrow's classes as an implementation detail. This is the single most valuable decision you'll make with this package — it makes the eventual migration to php-amqplib-direct or a real Laravel RabbitMQ driver a one-class change.

Publish: serialize yourself, always set headers. Burrow publishes raw strings; there's no serializer. Pick JSON and be consistent, and always send content_type, because the consumer side sees only bytes:

final class BurrowPublisher implements MessagePublisher
{
    public function publish(string $event, array $payload): void
    {
        $this->publisher->publish(
            json_encode($payload, JSON_THROW_ON_ERROR),
            $event,
            ['content_type' => 'application/json', 'x-retry' => 0]   // array headers: requires >= 4.6.0
        );
    }
}

Consume: keep the consumer a thin adapter, resolve the real work from the container. The consumer runs inside a long-lived CLI process, so anything you construct at boot is stale forever:

final class OrderCreatedConsumer implements QueueConsumer
{
    public function consume(Message $message): void
    {
        app(HandleOrderCreated::class)(json_decode($message->getBody(), true, 512, JSON_THROW_ON_ERROR));
    }

    public function getQueueName(): string { return 'orders.notifications'; }
}

Wire the worker as an artisan command, not a raw script. You want it in supervisor, with the same env loading as the app:

// php artisan burrow:consume
$driver  = app(DriverInterface::class);
$handler = (new HandlerBuilder($driver))->async()->build(app(OrderCreatedConsumer::class));
$worker  = new Worker(new QueueHandlingDaemon($driver, $handler, 'orders.notifications'));
$worker->run();

Treat HandlerBuilder as a policy decision, not boilerplate. The builder composes the handler stack that decides what happens on exception. The legacy default behaviour is ContinueOnFailureHandler, which the README itself now calls dangerous — it lets the worker keep consuming after an error instead of letting the message fail. For anything that touches money or state, build a stack that nacks (or drops to a dead-letter exchange) on failure, and make the consumer idempotent so a redelivery is safe. If you need retry, build it as a handler (inspect a retry header, republish with an incremented counter and a delay queue) rather than expecting the library to provide it.

Topology as a deploy step, never at request time. Put declareExchange / declareAndBindQueue / delete operations behind a dedicated php artisan mq:setup command (they're idempotent) and run it from your deploy pipeline. Declaring exchanges on the hot path couples request latency to broker admin operations and masks drift.

Events and metrics are your observability story — use them. Burrow emits DaemonStarted, DaemonStopped, MessageReceived, MessageConsumed via league/event. Two patterns that pay off immediately:

$emitter = new \League\Event\Emitter();
$emitter->useListenerProvider(new SendMetricListenerProvider(
    MetricServiceFactory::create('dogstatsd', $config, ['service' => 'orders', 'env' => app()->environment()])
));
// bridge into Laravel's dispatcher so the rest of the app can listen too
$emitter->addListener(new class(app('events')) { /* forward to Laravel events */ });

The built-in metrics (daemon.message_received, daemon.message_consumed, daemon.message_processing_time) give you throughput and latency for free; received - consumed is your stuck-consumer alarm.

Use SyncPublisher deliberately, not as a default. Sync means the publish path blocks waiting on the broker. That's appropriate for a CLI/admin path or a genuine RPC-style flow, and a poor fit for "publish from a web request." Decide per call site.

Test against a real broker, and against your interface in unit tests. Burrow's abstractions are thin enough that mocking them proves nothing. Keep a docker compose service with RabbitMQ in CI, run one publish→consume round-trip per driver you support (php-amqplib and, if you care, ext-amqp), and use your own MessagePublisher fake everywhere else.

Gotchas and Tips

  • Provenance is the biggest gotcha. Three stars, zero dependents, no release since 2021, single-vendor origin, no CVE process, no issue triage. Assume you own it. If you adopt, vendor it into a private repo or a fork with relaxed constraints, pin by exact version + commit, and put Dependabot on the fork. Write down a "replace or maintain" review date now — this risk compounds annually.
  • No retries, no dead-lettering, no failed-message store, no idempotency. If your actual requirement is "we need async work in Laravel," a maintained Laravel RabbitMQ queue driver gives you retries, failed_jobs, and worker supervision for free. Burrow is only defensible if the requirement is genuinely transport portability across php-amqplib/ext-amqp, or the surrounding app is not Laravel.
  • Driver divergence is hidden until it isn't. The php-amqplib and ext-amqp paths are not behaviourally identical — ack/nack timing, confirm mode, heartbeat and connection handling all differ. SSL via a config array landed in 4.5.0 for php-amqplib only; on the PECL path you must construct the driver manually. If you rely on the C extension for performance, run your contract tests on both paths in CI or you'll discover the differences in production.
  • Headers must be an array. Array-valued headers were only fixed in 4.6.0 (PR #34). Pin >=4.6.0 and don't pass scalars where the code expects an array.
  • ContinueOnFailureHandler is a trap. It exists purely for backward compatibility with pre-4.0 behaviour. It suppresses failures rather than surfacing them; unacked messages then pile up against the prefetch limit and the consumer silently stalls. Watch unacked in rabbitmqctl list_queues name messages_ready messages_unacknowledged — a climbing unacked count is the classic symptom.
  • The README is internally inconsistent. The QueueHandlingDaemon is shown with two different constructor signatures, and the events section links MessageStopped.php while naming the class MessageConsumed. Trust src/ over the README, and verify the queue-name argument the daemon takes versus what QueueConsumer::getQueueName() returns — a mismatch means your worker happily consumes the wrong queue and looks healthy while doing nothing.
  • Long-running workers need connection health you write yourself. Broker restarts, idle heartbeats, memory growth, and graceful shutdown on SIGTERM are all yours. Wrap the run loop with sane reconnect-on-failure, keep the consumer stateless, and make sure the framework's DB connections are reconnected (or DB::purge() between messages) since the process never reboots.
  • Publishing from a web request can lose messages silently. With confirms off, a broker hiccup drops the message with no exception. Wrap publish in try/catch, decide explicitly whether the user request should fail, and consider a transactional outbox if message loss isn't acceptable. Never let an AMQP timeout blow up a user-facing 200.
  • Debugging checklist, in order: management UI queue state → rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers → your MessageReceived/MessageConsumed metric delta → the event listener that logs $message->getBody() and headers. Nine times out of ten it's a routing key or a missing binding, not the library.
  • Extension points worth knowing: custom implementations of the driver interface (behind DriverFactory) for connection pooling or instrumentation; custom handlers in the HandlerBuilder stack for retry/DLQ/logging; custom listeners on the league/event emitter for tracing; a custom metric backend if StatsD/DogStatsD don't fit. All four are small interfaces — prefer extending there over editing vendor/.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator