# Laravel RabbitMQ Events A package for working with RabbitMQ messages as Laravel events. Automatically serialize and publish events to RabbitMQ, and consume messages from queues by converting them back into events. ## Requirements - PHP 8.1+ - Laravel 10, 11 or 12 - RabbitMQ server ## Installation 1. Install via Composer: ```bash composer require diffhead/laravel-rabbitmq ``` 2. Register package's service provider ```php // app/providers.php return [ \Diffhead\PHP\LaravelRabbitMQ\ServiceProvider::class ]; ``` ## Usage ### 1. Creating regular events for publishing to a RabbitMQ Create an event that implements the `Broadcast` interface. `Broadcast` interface extends `JsonSerializable` then your event should implement `jsonSerialize` method. ```php namespace App\Events; use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast; use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent; use Illuminate\Foundation\Events\Dispatchable; class UserCreated implements Broadcast { use Dispatchable, BroadcastEvent; public function __construct( public int $userId, public string $email, public string $name, ) {} public function jsonSerialize(): array { return [ 'userId' => $this->userId, 'email' => $this->email, 'name' => $this->name, ]; } } ``` #### Event Parameters When implementing the `Broadcast` interface, you must define the following methods for targeting RabbitMQ message: - `getConnection(): string` — connection name defined in configuration - `getQueue(): string` — Queue name - `getExchange(): string` — Exchange name - `getExchangeType(): string` — Exchange type (direct, topic, fanout, headers) - `getExchangeIsDefault(): bool` — Whether to use the default exchange - `getRoutingKey(): string` — Routing key for the message #### Using the BroadcastEvent trait The `BroadcastEvent` trait provides implementations of all methods using default parameters from configuration. You can directly override method's return value for customizing concrete event or override this behaviour globally by changing following env variables: * `RABBITMQ_EVENT_CONNECTION=default` * `RABBITMQ_EVENT_QUEUE=default` * `RABBITMQ_EVENT_EXCHANGE=amq.direct` * `RABBITMQ_EVENT_EXCHANGE_TYPE=direct` * `RABBITMQ_EVENT_EXCHANGE_IS_DEFAULT=true` * `RABBITMQ_EVENT_ROUTING_KEY=` ```php namespace App\Events; use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast; use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent; class UserCreated implements Broadcast { use BroadcastEvent; } ``` If you need special parameters for a specific event, override the necessary methods: ```php namespace App\Events; use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast; use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent; class CriticalAlert implements Broadcast { use BroadcastEvent; public function getRoutingKey(): string { return 'alert.critical'; } public function getExchange(): string { return 'alerts.topic'; } public function getExchangeType(): string { return 'topic'; } } ``` #### Publishing Events Events are automatically published when dispatched. `\Diffhead\PHP\LaravelRabbitMQ\Listener\PublishEvent` This listener implements `ShouldQueue` interface then you can setup his behaviour by chaning following env variables: * `RABBITMQ_EVENT_PUBLISHING_CONNECTION=sync` * `RABBITMQ_EVENT_PUBLISHING_QUEUE=default` Example: ```php App\Events\UserCreated::dispatch( userId: 1, email: 'user@example.com', name: 'John Doe' ); ``` ### 2. Consuming Messages from RabbitMQ #### Map events Add regular laravel's application event classes mapping to RabbitMQ queues and routing keys using `rabbitmq.event.map` section. Event should implement `\Diffhead\PHP\LaravelRabbitMQ\Interface\Event` with no methods if you are using default library mapper. ```php // config/rabbitmq.php 'map' => [ \App\Events\User\UserCreated::class => [ 'queues' => ['portal.users'], 'routing_keys' => ['user.created'], ], \App\Events\Meeting\MeetingCreated::class => [ 'queues' => ['portal.meetings'], 'routing_keys' => ['meeting.created'], ], ], ``` You can set empty array for matching event of all queues or routing keys. Follow this example for mapping event with `amq.direct` exchange which not uses routing keys: ```php // config/rabbitmq.php 'map' => [ \App\Events\User\UserCreated::class => [ 'queues' => ['portal.users'], 'routing_keys' => [], ], ], ``` #### Consume RabbitMQ queues Use the `rabbitmq:consume` command to listen for messages. Consumer will listen rabbitmq bus and emit mapped event from as regular laravel event. ```bash ##################################################################################### # # Has following options: # # --connection=default - Connection name from config # --queue=default - Queue # --exchange=amq.direct - Exchange name # --exchange-type=direct - Exchange type # --exchange-is-default - Exchange is default, required for default exchanges # --routing-key=user.* - Listen routing keys, required for topic exchanges # --tag=myconsumer - Consumer tag for rabbitmq # ##################################################################################### php artisan rabbitmq:consume ``` ##### Full Consumer Startup Example ```bash php artisan rabbitmq:consume \ --connection=default \ --queue=service.users \ --exchange=amq.direct \ --exchange-type=direct \ --routing-key=user.* \ --tag=service-users-consumer ``` ##### Handling Received Events When a message is received from RabbitMQ, it is automatically deserialized and dispatched as a Laravel event. You can listen to these events normally: ```php namespace App\Listeners; use App\Events\UserCreated; use Illuminate\Support\Log; class LogUserCreation { public function handle(UserCreated $event): void { Log::info("User created: {$event->email}"); } } ``` Register the listener in `app/Providers/EventServiceProvider.php`: ```php protected $listen = [ \App\Events\UserCreated::class => [ \App\Listeners\LogUserCreation::class, ], ]; ``` or using Laravel's `\Illuminate\Support\Facades\Event` facade. ### 3. Configure and customize logics #### Serialization The package does serialization/deserialization of message data via following interfaces: * `\Diffhead\PHP\LaravelRabbitMQ\Interface\Serializer` * `\Diffhead\PHP\LaravelRabbitMQ\Interface\Unserializer` By default implementations library interacts with JSON data. ##### Custom Serialization You can use your own serialization classes by implementing interfaces and overriding following configuration entities: ```php 'message' => [ 'serializer' => \App\Services\CustomSerializer::class, 'unserializer' => \App\Services\CustomUnserializer::class, ], ``` ##### Custom mapping You can use your own mapping logic by implementing EventMapper interface and overriding the following configuration entity: ```php 'event' => [ 'mapper' => \App\Services\CustomEventMapper::class, ] ``` #### Configuration Publish the configuration file if it not exists: ```bash php artisan vendor:publish --provider="Diffhead\PHP\LaravelRabbitMQ\ServiceProvider" ``` Used environment variables: ```env # RabbitMQ Connection RABBITMQ_HOST=localhost RABBITMQ_PORT=5672 RABBITMQ_USER=guest RABBITMQ_PASSWORD=guest RABBITMQ_VHOST=/ # Events publishing listener parameters RABBITMQ_EVENT_PUBLISHING_CONNECTION=sync RABBITMQ_EVENT_PUBLISHING_QUEUE=default # Default event parameters RABBITMQ_EVENT_CONNECTION=default RABBITMQ_EVENT_QUEUE=default RABBITMQ_EVENT_EXCHANGE=amq.direct RABBITMQ_EVENT_EXCHANGE_TYPE=direct RABBITMQ_EVENT_EXCHANGE_IS_DEFAULT=true RABBITMQ_EVENT_ROUTING_KEY= ``` ##### Example of `config/rabbitmq.php` ```php // config/rabbitmq.php return [ 'connections' => [ 'default' => [ 'host' => env('RABBITMQ_HOST', 'localhost'), 'port' => env('RABBITMQ_PORT', 5672), 'user' => env('RABBITMQ_USER', 'guest'), 'password' => env('RABBITMQ_PASSWORD', 'guest'), 'vhost' => env('RABBITMQ_VHOST', '/'), ], 'secondary' => [ 'host' => env('RABBITMQ_SECONDARY_HOST', 'localhost'), 'port' => env('RABBITMQ_SECONDARY_PORT', 5672), 'user' => env('RABBITMQ_SECONDARY_USER', 'guest'), 'password' => env('RABBITMQ_SECONDARY_PASSWORD', 'guest'), 'vhost' => env('RABBITMQ_SECONDARY_VHOST', '/'), ] ], 'message' => [ 'serializer' => \Diffhead\PHP\LaravelRabbitMQ\Service\Serializer::class, 'unserializer' => \Diffhead\PHP\LaravelRabbitMQ\Service\Unserializer::class, ], 'event' => [ 'defaults' => [ 'connection' => env('RABBITMQ_EVENT_CONNECTION', 'default'), 'queue' => env('RABBITMQ_EVENT_QUEUE', 'default'), 'exchange' => env('RABBITMQ_EVENT_EXCHANGE', 'amq.direct'), 'exchange_type' => env('RABBITMQ_EVENT_EXCHANGE_TYPE', 'direct'), 'exchange_is_default' => (bool) env('RABBITMQ_EVENT_EXCHANGE_IS_DEFAULT', true), 'routing_key' => env('RABBITMQ_EVENT_ROUTING_KEY', ''), ], 'publishing' => [ 'connection' => env('RABBITMQ_EVENT_PUBLISHING_CONNECTION', 'sync'), 'queue' => env('RABBITMQ_EVENT_PUBLISHING_QUEUE', 'default'), ], 'mapper' => \Diffhead\PHP\LaravelRabbitMQ\Service\EventMapper::class, 'map' => [ /** * Map events to queues and routing keys */ \App\Events\User\UserCreated::class => [ 'queues' => ['portal.users'], 'routing_keys' => ['user.created'], ], \App\Events\Meeting\MeetingCreated::class => [ 'queues' => ['portal.meetings'], 'routing_keys' => ['meeting.created'], ], ], ] ]; ```