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.
Architecture fit. Burrow is a framework-agnostic AMQP abstraction, not a Laravel package. There is no service provider, no publishable config/burrow.php, no Laravel queue driver, no Queue::push() bridge, no job serialization, no failed-job table integration, and no Horizon compatibility. What it actually provides is a DriverFactory that selects between php-amqplib and the ext-amqp C extension, plus helpers for declaring exchanges, binding queues, publishing (sync/async), and a daemon/worker loop with a League Events emitter for metrics. For a Laravel application, that places it below the framework's queue layer — it is a transport-adapter utility, not an application-level messaging solution. It fits only if we deliberately intend to own raw AMQP semantics (routing keys, exchange topology, ack/nack, prefetch) rather than delegating to Laravel's queue abstraction.
Integration feasibility. The install is trivial (composer require evaneos/burrow) and the API surface in the README is small and readable — a competent PHP engineer can wire a spike in under a day. The genuine feasibility constraint is dependency age: the last release is October 2021 (v4.6.0), so the composer.json constraints on PHP and php-amqplib almost certainly predate PHP 8.2/8.3 and php-amqplib v3.x. In a modern Laravel 10/11 codebase, Composer resolution is the most likely first blocker — and --ignore-platform-reqs is not an acceptable production answer. Secondary feasibility gap: the library depends on evaneos/daemon for Worker/CLICommand, which is a second unmaintained transitive dependency from the same single vendor, effectively doubling the provenance surface.
Technical risk. Provenance dominates. Three stars, zero dependents, no release in over three years, single-vendor origin (Evaneos), no security response process, no deprecation policy, and no community validation. Compounding this, the README points to Scrutinizer badges as the quality signal — a CI service that no longer reflects a maintained pipeline, so we cannot treat "build passing" as evidence of current correctness. The offsetting factor is that the abstraction is small and shallow: replacing it with direct php-amqplib usage or a maintained Laravel RabbitMQ queue driver is a bounded refactor provided we enforce the boundary from day one. Additional risks: undocumented behavioural differences between the php-amqplib and ext-amqp paths (confirm mode, heartbeat, ack semantics), and an explicit warning in the README that ContinueOnFailureHandler is "quite dangerous" — a signal that error-handling policy was historically under-specified and remains the consumer's responsibility.
Key questions.
php-amqplib and ext-amqp, or simply "we need asynchronous work in Laravel"? If the latter, this is the wrong tool and vladimir-yuldashev/laravel-queue-rabbitmq plus Laravel's native queue system is the correct default.php-amqplib, the abstraction earns nothing and we should use php-amqplib directly.php-amqplib versions without constraint surgery?queue:work)? These are different operational contracts.Stack fit. Adopt Burrow only behind a narrow internal port — an application-owned MessagePublisher and MessageConsumer interface — and never let DriverFactory, Burrow value objects, or League\Event types leak into domain or controller code. For a Laravel application, the higher-fit default remains the framework queue system with a maintained RabbitMQ driver, which delivers retries, failed_jobs persistence, queue:retry, worker signals, and observability without bespoke code. Burrow is defensible in two narrow scenarios: (a) the requirement is genuinely transport-swappable raw AMQP, or (b) we are integrating with a pre-existing RabbitMQ topology that Laravel's queue conventions cannot express — custom exchange types, non-standard routing, or cross-language consumers with fixed contracts.
Migration path. Sequence it as a reversible spike, not a commitment. (1) Define the internal messaging interface against current application needs (publish(routingKey, payload, headers), consume(queue, handler)), written without reference to Burrow's API. (2) Implement two adapters against that interface: one Burrow-backed, one php-amqplib-direct (or Laravel-queue-backed). (3) Run both against the same contract test suite, including a real RabbitMQ container. (4) Only if Burrow wins on a concrete criterion — typically the ext-amqp path or an existing topology constraint — vendor it into a private Composer repository or a maintained fork with relaxed constraints, pinned by exact version and commit hash. This makes a later swap a single-class change rather than a migration project.
Compatibility. Verify explicitly, in this order, before any commitment: PHP constraint versus our runtime; php-amqplib major version alignment (v2 vs v3 is a breaking difference); ext-amqp availability across our container images, CI runners, and production hosts — if the C extension is not installed everywhere, the abstraction's core value proposition evaporates; symfony/console version compatibility, since v4.0.3's release notes show it was a moving target; and whether the shipped examples actually execute. Replace badge-trust with a smoke test in our own pipeline: bring up RabbitMQ, declare an exchange, bind a queue, publish, consume one message, assert receipt.
Sequencing. Timebox a two-day spike gated on "resolves and runs on our target PHP without constraint hacks." If that gate fails, stop and select the Laravel-native path — the decision is made cheaply. If it passes, pilot on exactly one low-risk asynchronous flow (notification dispatch, audit event emission) with no critical-path dependency. Do not adopt on payments, order state transitions, or anything requiring at-least-once guarantees until the failure modes below have been observed and handled in production-like conditions. Establish the fork-and-own decision at pilot time, not after wider rollout.
Laravel-specific wiring. Because there is no service provider, we must build the container bindings ourselves: a singleton for the driver, config sourced from config/messaging.php (env-driven, not hardcoded host/port as the README examples show), and a Console\Command wrapper for the daemon loop rather than invoking Worker::run() from a script. Note the README example passes 'host' => 'default' in several snippets — a copy-paste artifact that will silently fail; treat the examples as illustrative rather than runnable. Metrics integration requires manually constructing a League\Event\Emitter and the StatsD/DogStatsD MetricService, which duplicates observability infrastructure Laravel already provides — decide early whether to bridge into Laravel's event system or run both stacks in parallel.
Maintenance. Assume we own it entirely. Budget for a fork as a standing cost: dependency bumps, PHP version upgrades, php-amqplib major-version migrations, and occasional patches. The footprint is small but the obligation is permanent and compounds annually as the 2021 constraints age out of the ecosystem. Automate with Dependabot against the fork and record an explicit calendar review — "replace with Laravel-native queue or continue maintaining" — at twelve months. Without that forcing function, this becomes orphaned code that nobody dares upgrade.
Support. No upstream support channel, no issue triage, no CVE process, no security advisories. Internal AMQP expertise becomes the only escalation path. Identify at least two engineers who can debug connection drops, channel-level errors, ack/nack semantics, and prefetch misconfiguration; a single-person dependency on raw AMQP knowledge is a genuine operational hazard, particularly during an incident at an inconvenient hour. Document the topology (exchanges, queues, bindings, routing keys) as code and in a runbook — with no upstream docs beyond the README, our own documentation is the only institutional memory.
Scaling. Scaling characteristics derive from RabbitMQ topology and consumer design, not from Burrow. The library will not protect us from unbounded prefetch, one channel per message, missing publisher confirms, or hot queues. Throughput tuning happens in RabbitMQ (queue type, consumer count, prefetch) and in our worker wrapper. If we take the ext-amqp path for performance, we must validate that the throughput gain is actually measurable against our workload before accepting the portability cost — otherwise we are paying abstraction tax for no benefit. Long-running daemons require explicit ownership of connection health: heartbeat configuration, reconnect-with-backoff on failure, and clean shutdown on SIGTERM so in-flight messages are nacked rather than lost. Laravel's queue:work handles this; Burrow's Worker does not, by default.
Failure modes. Expect, and pre-mitigate: silent message loss on publish when publisher confirms are not enabled; unacked messages accumulating when consumer exceptions are not caught and explicitly nacked (the README's own warning about ContinueOnFailureHandler signals that default error policy was historically unsafe); behavioural divergence between the php-amqplib and ext-amqp code paths that surfaces only in production under load; ext-amqp missing from a newly built container image, failing at boot rather than at build; and Composer resolution breaking on a transitive dependency bump elsewhere in the monorepo. None of these are exotic — they are standard AMQP hazards that a maintained framework integration has already solved and that we would be re-solving manually.
Ramp-up. Low for engineers fluent in AMQP concepts (exchanges, bindings, routing keys, ack/nack, prefetch) — the API is small and the examples are legible. Materially higher for engineers whose mental model is Laravel's job/dispatch abstraction: Burrow exposes raw transport concerns Laravel normally hides, and the gap between "push a job" and "publish to an exchange with a routing key and manage acknowledgement" is where incidents originate. Budget a half-day of onboarding plus a working local RabbitMQ (the README's docker run -d -p 5672:5672 rabbitmq is sufficient) before anyone touches a shared environment. Realistically: one engineer-days for a spike, one engineer-week for a production-hardened adapter including confirm handling, reconnect logic, contract tests, and runbook.
How can I help you explore Laravel packages today?