ahmedosamir732 / eventbus-php
A Laravel package for publishing and consuming integration events over RabbitMQ with built-in retry, DLQ, and multi-tenant support.
Requires
- php: ^8.3
- illuminate/console: ^11.0|^12.0
- illuminate/log: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- php-amqplib/php-amqplib: ^3.7
- ramsey/uuid: ^4.7
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
README
A Laravel package for publishing and consuming integration events over RabbitMQ. Supports multi-tenant contexts, automatic retry with exponential backoff, dead-letter queues, and graceful shutdown.
Requirements
- PHP 8.3+
- Laravel 11 or 12
- RabbitMQ 3.7+
Installation
composer require ahmedosamir732/eventbus-php
Publish the config:
php artisan vendor:publish --tag=eventbus-config
Configuration
Add these to your .env:
RABBITMQ_HOST=rabbitmq RABBITMQ_PORT=5672 RABBITMQ_USER=guest RABBITMQ_PASSWORD=guest RABBITMQ_VHOST=/ EVENTBUS_SOURCE=my-service EVENTBUS_DRIVER=rabbitmq
The driver defaults to rabbitmq when RABBITMQ_HOST is set, otherwise falls back to null (no-op).
Publishing Events
Implement ContextProviderInterface to supply tenant, workspace, actor, and correlation info from the current request:
use Idaratech\EventBus\Contracts\ContextProviderInterface; class AppContextProvider implements ContextProviderInterface { public function getTenantId(): int|string|null { /* ... */ } public function getWorkspace(): string { /* ... */ } public function getActor(): array { /* ... */ } public function getCorrelationId(): ?string { /* ... */ } }
Register it in config/eventbus.php:
'context_provider' => App\EventBus\AppContextProvider::class,
Then publish events:
use Idaratech\EventBus\Contracts\EventBusInterface; use Idaratech\EventBus\IntegrationEventFactory; class SomeService { public function __construct( private EventBusInterface $bus, private IntegrationEventFactory $factory, ) {} public function doWork(): void { $event = $this->factory->create( eventType: 'user.created', version: '1.0', payload: ['user_id' => 123, 'email' => 'user@example.com'], ); $this->bus->publish('my-exchange', $event); } }
Consuming Events
Implement EventHandlerInterface and tag it in a service provider:
use Idaratech\EventBus\Contracts\EventHandlerInterface; use Idaratech\EventBus\Contracts\IntegrationEventInterface; class UserCreatedHandler implements EventHandlerInterface { public function supports(string $eventType): bool { return $eventType === 'user.created'; } public function handle(IntegrationEventInterface $event): void { $payload = $event->getPayload(); // Process the event... } }
Register the handler:
$this->app->tag([UserCreatedHandler::class], 'eventbus.handlers');
Configure consumers in config/eventbus.php:
'consumers' => [ [ 'queue' => 'my-events', 'retryExchange' => 'my-events.retry', 'dlqRoutingKey' => 'my-events.dlq', 'maxRetries' => 3, 'retryDelays' => [1000, 5000, 25000], 'prefetchCount' => 1, ], ],
Start the consumer:
php artisan eventbus:consume --memory-limit=128 --timeout=0
DLQ Replay
Replay dead-letter messages:
php artisan eventbus:replay-dlq
Event Envelope
Every event carries a standardized envelope:
| Field | Type | Description |
|---|---|---|
event_id |
string (UUID) | Unique event identifier |
event_type |
string | Routing key (e.g. user.created) |
version |
string | Schema version |
source |
string | Publishing service name |
tenant_id |
int|string|null | Tenant context |
workspace |
string | Workspace/environment |
correlation_id |
string|null | Request trace ID |
actor |
array | Who triggered the event |
payload |
array | Event-specific data |
timestamp |
string (ISO 8601) | When the event was created |
metadata |
array | Retry count, original timestamp |
Lifecycle Hooks
Register before/after hooks in config:
'hooks' => [ 'before_handle' => function (IntegrationEventInterface $event) { // Initialize tenant context, set log context, etc. }, 'after_handle' => function (?IntegrationEventInterface $event) { // Clean up tenant context, flush logs, etc. }, ],
NullEventBus
When EVENTBUS_DRIVER=null (or RABBITMQ_HOST is unset), a NullEventBus is bound that silently discards all published events. Useful for local development and testing.
License
MIT