genhoi/humus-amqp-bundle
genhoi/humus-amqp-bundle brings AMQP messaging to PHP apps, with straightforward configuration for RabbitMQ producers and consumers so you can publish and process messages with minimal boilerplate.
This is a Symfony bundle, not a Laravel package. There is no service provider, no Laravel auto-discovery, and no config/humus_amqp.php. A Laravel app cannot composer require this and expect it to work — the assessment below is written for a Laravel developer who either (a) is being pulled into a Symfony codebase that already depends on it, or (b) wants to mine its DI/topology patterns for a Laravel AMQP integration. For greenfield Laravel work, use vladimir-yuldashev/laravel-queue-rabbitmq (built on php-amqplib) instead; the runtime model is different and not interchangeable.
ext-amqp, not php-amqplib:
pecl install amqp && docker-php-ext-enable amqp
composer require genhoi/humus-amqp-bundle (it pulls prolic/humus-amqp). v2.3.0 adds Symfony 7 component constraints, so on a Symfony 6.4/7.x host you should land on the current tag; on older hosts (5.4) Composer will resolve back to a pre-2.3 tag — check composer show genhoi/humus-amqp-bundle after install and read the schema for that version.config/bundles.php:
Genhoi\HumusAmqpBundle\HumusAmqpBundle::class => ['all' => true],
config/packages/humus_amqp.yaml (schema below).bin/console humus:consume <consumer_id> — confirm the exact command name with bin/console list humus, since the CLI is registered by the bundle and is thin.Because adoption is essentially zero (single-digit stars, no known dependents), the docs are not the source of truth — the code is:
| Look at | Why |
|---|---|
vendor/genhoi/humus-amqp-bundle/DependencyInjection/Configuration.php |
The exact config schema for the version you pinned. Every key below must be verified there. |
vendor/genhoi/humus-amqp-bundle/Resources/config/*.xml (or .php) |
Service ids, tags, and the console command registration. |
vendor/prolic/humus-amqp/docs/ |
Runtime semantics: Producer, Consumer, Envelope, ConsumerManager. |
# config/packages/humus_amqp.yaml
humus_amqp:
connections:
default:
host: '%env(RABBITMQ_HOST)%'
port: 5672
login: '%env(RABBITMQ_USER)%'
password: '%env(RABBITMQ_PASSWORD)%'
vhost: /
exchanges:
orders:
name: orders
type: topic
connection: default
queues:
order_created:
name: order_created
connection: default
exchange: orders
routing_keys: ['order.created']
producers:
order_publisher:
class: Humus\Amqp\Producer
exchange: orders
connection: default
consumers:
order_created_consumer:
class: Humus\Amqp\Consumer
queue: order_created
connection: default
callback: App\Amqp\OrderCreatedHandler
Then bin/console humus:consume order_created_consumer. That is the whole day-one loop.
The bundle's real value is that exchanges, queues, bindings, producers, and consumers are declared once in YAML and materialised as container services. Your application code never touches AMQPExchange / AMQPQueue directly — it injects a configured producer by id and hands it an envelope.
use Humus\Amqp\Producer;
use Humus\Amqp\JsonEnvelope;
final class OrderPlacedPublisher
{
public function __construct(private Producer $producer) {}
public function publish(string $orderId): void
{
$this->producer->publish(new JsonEnvelope(['order_id' => $orderId]), 'orders');
}
}
Wire the concrete producer via a service alias in config/services.yaml (confirm the generated service id with bin/console debug:container --show-private | grep humus):
services:
App\Amqp\OrderPlacedPublisher:
arguments: ['@humus_amqp.producer.order_publisher']
The bundle expects the callback key to reference a container service (typically !service or a class name resolved through the container). Keep handlers as plain invokable services so they are unit-testable without AMQP:
final class OrderCreatedHandler
{
public function __invoke(Envelope $envelope, Consumer $consumer): void
{
// idempotent work here
$consumer->acknowledge($envelope);
}
}
The exact callback arity differs across HumusAmqp versions — check the Consumer::setCallback call site in the bundle before writing the signature.
For a small deployment, use the ConsumerManager (or the bundle's console command with multiple ids) to run several consumers in a single supervisor process instead of one process per queue. This is cheaper but couples failure domains — a fatal in one callback kills all consumers in that process.
Day-to-day the consumer is a daemon. Standard shape:
autorestart=true.SIGTERM for graceful stop; the consumer loop should finish the current delivery, flush(), and exit.qos) low (1–10) on the consumer so a slow handler doesn't hoard messages.If you're porting the pattern into Laravel rather than using the bundle:
| HumusAmqpBundle concept | Laravel equivalent |
|---|---|
humus_amqp.producers.* + injected Producer |
Queue::connection('rabbitmq')->push(...) or a small publisher service bound in a provider |
consumers.*.callback service |
php artisan queue:work + a Job class handle() |
humus:consume |
queue:work --queue=... |
| YAML topology declarations | config/queue.php + a topology setup command, or rabbitmqadmin definitions file |
The topology-as-config idea is worth copying; the runtime is not.
Hard blockers
ext-amqp is mandatory. It is a PECL C extension that wraps librabbitmq. If your target runtime (Heroku, some shared hosts, AWS Lambda) can't load it, this bundle is a dead end. php-amqplib-based packages (the Laravel ecosystem default) have no such constraint.ServiceProvider.composer.json, commit composer.lock, and diff the source between upgrades.Config quirks
humus_amqp.yaml always run bin/console cache:clear, otherwise debug:container lies to you.orders and also declare a producer with id orders, you'll get a confusing override. Prefix ids (humus.producer.orders) if you have a large container.composer why-not symfony/framework-bundle 7 and composer update --dry-run genhoi/humus-amqp-bundle prolic/humus-amqp before committing; if prolic/humus-amqp (or any transitive package) still caps at Symfony 6, you'll get a resolution failure that looks like a bundle problem but isn't. Bump the bundle alone, not the framework, and keep the lockfile diff in the PR.Runtime pitfalls
configure permission on the vhost you'll get a 403 ACCESS_REFUSED at the producer, not at boot. Test with a low-privilege user before deploying.nack with requeue=false into a dead-letter exchange or explicitly reject. Configure the DLX via queue arguments (x-dead-letter-exchange) in config — the bundle passes arguments through.consume() indefinitely, SIGTERM won't be noticed until the next poll. Set a non-zero timeout (and idle timeout where supported) so the loop wakes up.unset() large payloads, avoid capturing the container in the callback, and schedule restarts.Extension points
class in config to inject your own implementation (e.g. a logging producer wrapping the default) rather than editing vendor code.ConsumerManager / console command is the seam for custom scheduling: if you need cron-style consumers, write your own Command that resolves humus_amqp.consumer.<id> and calls consume() itself — do not fork the vendor command.When to walk away
If the evaluation is "should we adopt this for a Laravel service?" the answer is no — use vladimir-yuldashev/laravel-queue-rabbitmq, or php-amqplib directly with a thin topology-definitions file. Revisit this bundle only if you are already inside a Symfony app where it is installed (v2.3.0 makes it viable on Symfony 6.4/7.x hosts), in which case the winning move is to read DependencyInjection/Configuration.php on day one and treat it as documentation.
How can I help you explore Laravel packages today?