Install Dependencies
composer require dimkabelkov/rabbit-bus-bundle
composer require emag-tech-labs/rabbitmq-bundle # Symfony 5
# OR
composer require php-amqplib/rabbitmq-bundle # Symfony 4
composer require symfony/monolog-bundle
Configure Environment
Add to .env for each service (e.g., APP_NAME=app-a):
APP_NAME=app-a
RABBITMQ_HOST=localhost
RABBITMQ_USER=guest
RABBITMQ_PASS=guest
Define Shared Event Classes
Create a shared package (e.g., you-app/bus-events) with events extending AbstractEvent:
namespace App\Events\Shared;
use Dimkabelkov\RabbitBusBundle\BusEvent\AbstractEvent;
class OrderCreatedEvent extends AbstractEvent
{
public const EXCHANGE = 'app.order.created';
public string $orderId;
}
Configure config/packages/rabbit_bus.yaml
For a multi-consumer app (handles multiple event types):
rabbit_bus:
events:
multiple: true
For a single-consumer app (handles specific events):
rabbit_bus:
events:
multiple: false
consumers:
- App\Events\Shared\OrderCreatedEvent
producers:
- App\Events\Shared\OrderCreatedEvent
Enable Monolog Channel
Add to config/packages/monolog.yaml:
monolog:
handlers:
main:
channels: ['%env(APP_NAME).rabbit-bus']
Dispatch an Event
In AppA:
use App\Events\Shared\OrderCreatedEvent;
$event = new OrderCreatedEvent();
$event->orderId = '123';
$this->bus->dispatch($event); // Auto-published to RabbitMQ
Run the Consumer Start the consumer in a separate terminal:
php bin/console rabbit-bus:consume
Producer Service (AppA)
OrderCreatedEvent after saving an order.$this->bus->dispatch(new OrderCreatedEvent($orderId));
Consumer Service (AppB)
multiple: true).OrderCreatedEvent to update stock.# config/packages/rabbit_bus.yaml (AppB)
rabbit_bus:
events:
multiple: false
consumers:
- App\Events\Shared\OrderCreatedEvent
Shared Event Contracts
you-app/bus-events) to ensure consistency across services.AbstractEvent and define EXCHANGE constants for routing.Error Handling
monolog:
handlers:
rabbit_bus:
type: stream
path: "%kernel.logs_dir%/%env(APP_NAME).rabbit-bus.log%"
level: error
channels: ['%env(APP_NAME).rabbit-bus']
Testing
RabbitBus service in unit tests:$this->bus = $this->createMock(RabbitBus::class);
$this->bus->expects($this->once())->method('dispatch');
Environment-Specific Configs
Override rabbit_bus config per service using environment variables:
# config/packages/rabbit_bus.yaml
rabbit_bus:
events:
multiple: "%env(bool:RABBIT_BUS_MULTIPLE)%"
consumers: "%env(var:RABBIT_BUS_CONSUMERS)%" # Comma-separated event classes
Dynamic Routing
Use event metadata (e.g., EXCHANGE) to route events to different queues:
class OrderCancelledEvent extends AbstractEvent
{
public const EXCHANGE = 'app.order.cancelled';
}
Retry Logic Implement a dead-letter queue (DLQ) for failed messages by configuring RabbitMQ bindings:
# rabbitmq-bundle config
rabbitmq:
connections:
default:
url: '%env(RABBITMQ_URL)%'
lazy: true
retry_strategy:
max_retries: 3
timeout: 1000
Performance Tuning
rabbit_bus:
consumer_options:
prefetch_count: 10
Event Class Mismatch
rabbit_bus:
event_classes:
- App\Events\Shared\OrderCreatedEvent
- App\Events\Shared\OrderCancelledEvent
Consumer Stuck in "Busy" State
prefetch_count or optimize consumer logic. Use ack()/nack() explicitly:public function handle(AbstractEvent $event): void
{
try {
// Process event
$this->bus->ack($event);
} catch (\Exception $e) {
$this->bus->nack($event);
throw $e;
}
}
Monolog Not Logging
APP_NAME.rabbit-bus is in Monolog’s channels list and the handler is active.Duplicate Messages
Configuration Overrides
config/packages/rabbit_bus_local.yaml) may not apply.# config/packages/rabbit_bus.yaml
rabbit_bus: *default
Check Consumer Status
Monitor active consumers via RabbitMQ management UI (http://localhost:15672) or CLI:
rabbitmqctl list_consumers
Enable Debug Logging Temporarily add a debug handler to Monolog:
monolog:
handlers:
rabbit_bus_debug:
type: stream
path: "%kernel.logs_dir%/debug-rabbit-bus.log"
level: debug
channels: ['%env(APP_NAME).rabbit-bus']
Validate Event Serialization
Ensure events are serializable (avoid circular references or non-serializable properties). Use json_encode() to test:
$event = new OrderCreatedEvent();
json_encode($event); // Should not throw
Test Locally with Docker Use a RabbitMQ container for isolation:
# docker-compose.yml
services:
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
Custom Event Handlers Override the default handler resolution by binding your own:
// src/RabbitBus/EventHandlerResolver.php
namespace App\RabbitBus;
use Dimkabelkov\RabbitBusBundle\BusEvent\AbstractEvent;
use Dimkabelkov\RabbitBusBundle\EventHandlerResolverInterface;
class CustomHandlerResolver implements EventHandlerResolverInterface
{
public function resolve(AbstractEvent $event): string
{
return 'App\\Handler\\' . class_basename($event) . 'Handler';
}
}
Register in services:
services:
App\RabbitBus\CustomHandlerResolver:
tags: [rabbit_bus.event_handler_resolver]
Middleware for Events Add preprocessing/validation logic:
use Dimkabelkov\RabbitBusBundle\Middleware\MiddlewareInterface;
class
How can I help you explore Laravel packages today?