Version 1.0.0
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
vendor/
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright 2025 Viktor S. <thinlineseverywhere@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the “Software”),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
448
README.md
Normal file
448
README.md
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**Automatic event serialization** — Events implementing the `Broadcast` interface are automatically serialized and sent to RabbitMQ
|
||||||
|
**Message consumption** — Command to consume messages from RabbitMQ with automatic deserialization and event dispatching
|
||||||
|
**Default bindings** — The `BroadcastEvent` trait sets standard connection parameters for an event
|
||||||
|
**Flexible configuration** — Support for multiple connections and parameterization for each event
|
||||||
|
**Microservices architecture** — Ideal for data exchange between services
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- PHP 8.1+
|
||||||
|
- Laravel 10, 11 or 12
|
||||||
|
- RabbitMQ server
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install via Composer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require diffhead/laravel-rabbitmq
|
||||||
|
```
|
||||||
|
|
||||||
|
The package will be automatically registered thanks to Laravel Service Provider Discovery.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Publish the configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan vendor:publish --provider="Diffhead\PHP\LaravelRabbitMQ\ServiceProvider"
|
||||||
|
```
|
||||||
|
|
||||||
|
ThisEnvironment Variables
|
||||||
|
|
||||||
|
```env
|
||||||
|
# RabbitMQ Connection
|
||||||
|
RABBITMQ_HOST=localhost
|
||||||
|
RABBITMQ_PORT=5672
|
||||||
|
RABBITMQ_USER=guest
|
||||||
|
RABBITMQ_PASSWORD=guest
|
||||||
|
RABBITMQ_VHOST=/
|
||||||
|
|
||||||
|
# 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=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```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', ''),
|
||||||
|
],
|
||||||
|
'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'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### 1. Creating an Event for Publishing to RabbitMQ
|
||||||
|
|
||||||
|
Create an event that implements the `Broadcast` interface:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
- `getConnection(): string` — RabbitMQ connection name
|
||||||
|
- `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:
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
||||||
|
|
||||||
|
class UserCreated implements Broadcast
|
||||||
|
{
|
||||||
|
use BroadcastEvent;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public int $userId,
|
||||||
|
public string $email,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'userId' => $this->userId,
|
||||||
|
'email' => $this->email,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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 __construct(
|
||||||
|
public string $message,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getRoutingKey(): string
|
||||||
|
{
|
||||||
|
return 'alert.critical';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExchange(): string
|
||||||
|
{
|
||||||
|
return 'alerts.topic';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExchangeType(): string
|
||||||
|
{
|
||||||
|
return 'topic';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'message' => $this->message,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Publishing Events
|
||||||
|
|
||||||
|
Events are automatically published when dispatched:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Events\UserCreated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events implementing Broadcast are automatically sent to RabbitMQ
|
||||||
|
*/
|
||||||
|
UserCreated::dispatch(userId: 1, email: 'user@example.com', name: 'John Doe');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Consuming Messages from RabbitMQ
|
||||||
|
|
||||||
|
Use the `rabbitmq:consume` command to listen for messages:
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 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 SendWelcomeEmail
|
||||||
|
{
|
||||||
|
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\SendWelcomeEmail::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Event Publishing Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Laravel Event (Broadcast)
|
||||||
|
↓
|
||||||
|
PublishEvent (Listener)
|
||||||
|
↓
|
||||||
|
Serializer (JSON)
|
||||||
|
↓
|
||||||
|
RabbitMQ Exchange
|
||||||
|
↓
|
||||||
|
Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message Consumption Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
RabbitMQ Queue
|
||||||
|
↓
|
||||||
|
Message Consumer
|
||||||
|
↓
|
||||||
|
Unserializer (JSON)
|
||||||
|
↓
|
||||||
|
EventMapper (Event)
|
||||||
|
↓
|
||||||
|
EventEmitter (Service)
|
||||||
|
↓
|
||||||
|
Event Listeners
|
||||||
|
```
|
||||||
|
|
||||||
|
## Microservices Architecture Example
|
||||||
|
|
||||||
|
### Service 1: Publishes event
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
||||||
|
|
||||||
|
class UserCreated implements Broadcast
|
||||||
|
{
|
||||||
|
use BroadcastEvent;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public string $email,
|
||||||
|
public string $name,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getRoutingKey(): string
|
||||||
|
{
|
||||||
|
return 'user.created';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'email' => $this->email,
|
||||||
|
'name' => $this->name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Events\UserCreated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller method
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$user = User::create($request->validated());
|
||||||
|
UserCreated::dispatch($user->id, $user->email, $user->name);
|
||||||
|
|
||||||
|
return response()->json($user, 201);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service 2: Receives event
|
||||||
|
|
||||||
|
Map event using configuration:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'map' => [
|
||||||
|
\App\Events\UserCreated::class => [
|
||||||
|
'queues' => ['service2.users'],
|
||||||
|
'routing_keys' => ['user.created']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then implement and register event listener:
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Events\UserCreated;
|
||||||
|
|
||||||
|
class SyncUserToCalendar
|
||||||
|
{
|
||||||
|
public function handle(UserCreated $event): void
|
||||||
|
{
|
||||||
|
CalendarUser::create([
|
||||||
|
'external_id' => $event->id,
|
||||||
|
'email' => $event->email,
|
||||||
|
'name' => $event->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Start consumer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan rabbitmq:consume --queue=service2.users --routing-key=user.* --exchange=amq.topic --exchange-type=topic --exchange-is-default
|
||||||
|
```
|
||||||
|
|
||||||
|
## Serialization
|
||||||
|
|
||||||
|
The package uses JSON for serialization/deserialization of data via `Serializer` and `Unserializer` interfaces.
|
||||||
|
|
||||||
|
### 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,
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mapping
|
||||||
|
|
||||||
|
The package maps rabbitmq message to application events
|
||||||
|
|
||||||
|
### 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,
|
||||||
|
]
|
||||||
|
```
|
||||||
42
composer.json
Normal file
42
composer.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "diffhead/laravel-rabbitmq",
|
||||||
|
"description": "A laravel package for events emitting between services using RabbitMQ as message broker.",
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"keywords": [
|
||||||
|
"laravel", "rabbitmq", "event", "emit", "microservice",
|
||||||
|
"pipeline", "data exchanging", "message broker"
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Diffhead\\PHP\\LaravelRabbitMQ\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Diffhead\\PHP\\LaravelRabbitMQ\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"laravel/framework": "^10 || ^11.0 || ^12.0",
|
||||||
|
"php-amqplib/php-amqplib": "^3.7",
|
||||||
|
"diffhead/php-dto": "^1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit",
|
||||||
|
"post-install-cmd": [
|
||||||
|
"php artisan vendor:publish --provider=\"Diffhead\\PHP\\LaravelRabbitMQ\\ServiceProvider\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Viktor S.",
|
||||||
|
"email": "thinlineseverywhere@gmail.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6296
composer.lock
generated
Normal file
6296
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
config/rabbitmq.php
Normal file
43
config/rabbitmq.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?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', '/'),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'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' => (string) env('RABBITMQ_EVENT_ROUTING_KEY', ''),
|
||||||
|
],
|
||||||
|
'mapper' => \Diffhead\PHP\LaravelRabbitMQ\Service\EventMapper::class,
|
||||||
|
'map' => [
|
||||||
|
/**
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* \App\Shared\Event\User\UserCreated::class => [
|
||||||
|
* 'queues' => ['portal.calendar.users'],
|
||||||
|
* 'routing_keys' => ['user.created'],
|
||||||
|
* ],
|
||||||
|
* \App\Shared\Event\Meeting\MeetingCreated::class => [
|
||||||
|
* 'queues' => ['portal.calendar.meetings'],
|
||||||
|
* 'routing_keys' => ['meeting.created'],
|
||||||
|
* ],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
|
||||||
|
]
|
||||||
|
];
|
||||||
118
src/Command/Consume.php
Normal file
118
src/Command/Consume.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Command;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Dto\ConsumerParameters;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Connection;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Exchange;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\ExchangeDeclaration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\QueueBindings;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\QueueDeclaration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Configuration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Connector;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Message;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class Consume extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'rabbitmq:consume {--connection=} {--queue=} {--exchange=} {--exchange-type=} {--exchange-is-default} {--routing-key=} {--tag=}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Consume messages from RabbitMQ';
|
||||||
|
|
||||||
|
private ?Connection $connection = null;
|
||||||
|
|
||||||
|
public function handle(
|
||||||
|
Configuration $configuration,
|
||||||
|
Connector $connector,
|
||||||
|
Message $handler,
|
||||||
|
LoggerInterface $logger
|
||||||
|
): void {
|
||||||
|
$params = ConsumerParameters::fromArray($this->options());
|
||||||
|
$config = $configuration->get($params->connection->value() ?? 'default');
|
||||||
|
|
||||||
|
$queue = $this->getQueue($params);
|
||||||
|
|
||||||
|
$this->connection = $connector->connect($config, $queue);
|
||||||
|
|
||||||
|
$tag = $params->tag->value() ?? 'rabbitmq-laravel-consumer';
|
||||||
|
|
||||||
|
$consumer = function (AMQPMessage $message) use ($handler, $queue, $logger): void {
|
||||||
|
try {
|
||||||
|
$this->info(sprintf('Received message: %s', $message->getBody()));
|
||||||
|
|
||||||
|
$mergedQueue = $this->getMergedQueue($queue, $message);
|
||||||
|
|
||||||
|
$handler->handle($mergedQueue, $message);
|
||||||
|
$message->ack();
|
||||||
|
|
||||||
|
$this->info('Message processed successfully.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$message->nack();
|
||||||
|
|
||||||
|
$this->error(
|
||||||
|
sprintf('Processing error: %s', $e->getMessage())
|
||||||
|
);
|
||||||
|
|
||||||
|
$logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->connection->channel()->basic_consume(
|
||||||
|
queue: $queue->name(),
|
||||||
|
consumer_tag: $tag,
|
||||||
|
callback: $consumer
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->connection->channel()->consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->connection?->channel()->close();
|
||||||
|
$this->connection?->connection()->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQueue(ConsumerParameters $params): Queue
|
||||||
|
{
|
||||||
|
$routingKey = $params->routingKey->value() ?? '';
|
||||||
|
|
||||||
|
return new Queue(
|
||||||
|
name: $params->queue->value() ?? 'default',
|
||||||
|
exchange: new Exchange(
|
||||||
|
name: $params->exchange->value() ?? 'amq.direct',
|
||||||
|
type: $params->exchangeType->value() ?? 'direct',
|
||||||
|
isDefault: $params->exchangeIsDefault->value(),
|
||||||
|
declaration: new ExchangeDeclaration()
|
||||||
|
),
|
||||||
|
declaration: new QueueDeclaration(),
|
||||||
|
bindings: new QueueBindings($routingKey),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMergedQueue(Queue $queue, AMQPMessage $message): Queue
|
||||||
|
{
|
||||||
|
return new Queue(
|
||||||
|
name: $queue->name(),
|
||||||
|
exchange: $queue->exchange(),
|
||||||
|
declaration: $queue->declaration(),
|
||||||
|
bindings: new QueueBindings(
|
||||||
|
routingKey: $message->getRoutingKey(),
|
||||||
|
nowait: $queue->bindings()->nowait(),
|
||||||
|
arguments: $queue->bindings()->arguments(),
|
||||||
|
ticket: $queue->bindings()->ticket()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Dto/ConsumerParameters.php
Normal file
28
src/Dto/ConsumerParameters.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Dto;
|
||||||
|
|
||||||
|
use Diffhead\PHP\Dto\Dto;
|
||||||
|
use Diffhead\PHP\Dto\Property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $connection
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $queue
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $exchange
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $routingKey
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $exchangeType
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<bool> $exchangeIsDefault
|
||||||
|
* @property \Diffhead\PHP\Dto\Property<string|null> $tag
|
||||||
|
*/
|
||||||
|
class ConsumerParameters extends Dto
|
||||||
|
{
|
||||||
|
protected Property $connection;
|
||||||
|
protected Property $queue;
|
||||||
|
protected Property $exchange;
|
||||||
|
protected Property $routingKey;
|
||||||
|
protected Property $exchangeType;
|
||||||
|
protected Property $exchangeIsDefault;
|
||||||
|
protected Property $tag;
|
||||||
|
}
|
||||||
17
src/Event/Broadcast.php
Normal file
17
src/Event/Broadcast.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Event;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
interface Broadcast extends JsonSerializable
|
||||||
|
{
|
||||||
|
public function getConnection(): string;
|
||||||
|
public function getQueue(): string;
|
||||||
|
public function getExchange(): string;
|
||||||
|
public function getExchangeType(): string;
|
||||||
|
public function getExchangeIsDefault(): bool;
|
||||||
|
public function getRoutingKey(): string;
|
||||||
|
}
|
||||||
9
src/Exception/AssociatedEventNotFound.php
Normal file
9
src/Exception/AssociatedEventNotFound.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class AssociatedEventNotFound extends RuntimeException {}
|
||||||
13
src/Interface/EventMapper.php
Normal file
13
src/Interface/EventMapper.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use App\Shared\Event\Event;
|
||||||
|
|
||||||
|
interface EventMapper
|
||||||
|
{
|
||||||
|
public function map(Queue $queue, array $payload): Event;
|
||||||
|
}
|
||||||
12
src/Interface/Serializer.php
Normal file
12
src/Interface/Serializer.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Interface;
|
||||||
|
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
|
interface Serializer
|
||||||
|
{
|
||||||
|
public function serialize(object $data): AMQPMessage;
|
||||||
|
}
|
||||||
12
src/Interface/Unserializer.php
Normal file
12
src/Interface/Unserializer.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Interface;
|
||||||
|
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
|
interface Unserializer
|
||||||
|
{
|
||||||
|
public function unserialize(AMQPMessage $message): array;
|
||||||
|
}
|
||||||
62
src/Listener/PublishEvent.php
Normal file
62
src/Listener/PublishEvent.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Listener;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Serializer;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Exchange;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\ExchangeDeclaration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\QueueBindings;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\QueueDeclaration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Configuration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Connector;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
||||||
|
class PublishEvent implements ShouldQueue
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Configuration $configuration,
|
||||||
|
private Connector $connector,
|
||||||
|
private Serializer $serializer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(Broadcast $event): void
|
||||||
|
{
|
||||||
|
$config = $this->configuration->get($event->getConnection());
|
||||||
|
|
||||||
|
$queue = new Queue(
|
||||||
|
name: $event->getQueue(),
|
||||||
|
exchange: new Exchange(
|
||||||
|
name: $event->getExchange(),
|
||||||
|
type: $event->getExchangeType(),
|
||||||
|
isDefault: $event->getExchangeIsDefault(),
|
||||||
|
declaration: new ExchangeDeclaration()
|
||||||
|
),
|
||||||
|
declaration: new QueueDeclaration(),
|
||||||
|
bindings: new QueueBindings(
|
||||||
|
routingKey: $event->getRoutingKey()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$connection = $this->connector->connect($config, $queue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$message = $this->serializer->serialize($event);
|
||||||
|
|
||||||
|
$connection->channel()->basic_publish(
|
||||||
|
msg: $message,
|
||||||
|
exchange: $queue->exchange()->name(),
|
||||||
|
routing_key: $queue->bindings()->routingKey(),
|
||||||
|
);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
$connection->channel()->close();
|
||||||
|
$connection->connection()->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/Object/Configuration.php
Normal file
41
src/Object/Configuration.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class Configuration
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $host,
|
||||||
|
private int $port,
|
||||||
|
private string $user,
|
||||||
|
private string $password,
|
||||||
|
private string $vhost,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function host(): string
|
||||||
|
{
|
||||||
|
return $this->host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function port(): int
|
||||||
|
{
|
||||||
|
return $this->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): string
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function password(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function vhost(): string
|
||||||
|
{
|
||||||
|
return $this->vhost;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Object/Connection.php
Normal file
26
src/Object/Connection.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
use PhpAmqpLib\Channel\AMQPChannel;
|
||||||
|
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||||
|
|
||||||
|
class Connection
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AMQPStreamConnection $connection,
|
||||||
|
private AMQPChannel $channel
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function connection(): AMQPStreamConnection
|
||||||
|
{
|
||||||
|
return $this->connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function channel(): AMQPChannel
|
||||||
|
{
|
||||||
|
return $this->channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Object/Exchange.php
Normal file
35
src/Object/Exchange.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class Exchange
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name = 'amq.direct',
|
||||||
|
private string $type = 'direct',
|
||||||
|
private bool $isDefault = true,
|
||||||
|
private ExchangeDeclaration $declaration,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDefault(): bool
|
||||||
|
{
|
||||||
|
return $this->isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function declaration(): ExchangeDeclaration
|
||||||
|
{
|
||||||
|
return $this->declaration;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/Object/ExchangeDeclaration.php
Normal file
53
src/Object/ExchangeDeclaration.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class ExchangeDeclaration
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private bool $passive = false,
|
||||||
|
private bool $durable = true,
|
||||||
|
private bool $autoDelete = false,
|
||||||
|
private bool $internal = false,
|
||||||
|
private bool $nowait = false,
|
||||||
|
private array $arguments = [],
|
||||||
|
private mixed $ticket = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function passive(): bool
|
||||||
|
{
|
||||||
|
return $this->passive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function durable(): bool
|
||||||
|
{
|
||||||
|
return $this->durable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function internal(): bool
|
||||||
|
{
|
||||||
|
return $this->internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autoDelete(): bool
|
||||||
|
{
|
||||||
|
return $this->autoDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nowait(): bool
|
||||||
|
{
|
||||||
|
return $this->nowait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function arguments(): array
|
||||||
|
{
|
||||||
|
return $this->arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ticket(): ?int
|
||||||
|
{
|
||||||
|
return $this->ticket;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Object/Queue.php
Normal file
35
src/Object/Queue.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class Queue
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $name = 'default',
|
||||||
|
private Exchange $exchange,
|
||||||
|
private QueueDeclaration $declaration,
|
||||||
|
private QueueBindings $bindings,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exchange(): Exchange
|
||||||
|
{
|
||||||
|
return $this->exchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function declaration(): QueueDeclaration
|
||||||
|
{
|
||||||
|
return $this->declaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bindings(): QueueBindings
|
||||||
|
{
|
||||||
|
return $this->bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Object/QueueBindings.php
Normal file
35
src/Object/QueueBindings.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class QueueBindings
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $routingKey = '',
|
||||||
|
private bool $nowait = false,
|
||||||
|
private array $arguments = [],
|
||||||
|
private mixed $ticket = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function routingKey(): string
|
||||||
|
{
|
||||||
|
return $this->routingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nowait(): bool
|
||||||
|
{
|
||||||
|
return $this->nowait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function arguments(): array
|
||||||
|
{
|
||||||
|
return $this->arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ticket(): mixed
|
||||||
|
{
|
||||||
|
return $this->ticket;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/Object/QueueDeclaration.php
Normal file
53
src/Object/QueueDeclaration.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Object;
|
||||||
|
|
||||||
|
class QueueDeclaration
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private bool $passive = false,
|
||||||
|
private bool $durable = true,
|
||||||
|
private bool $exclusive = false,
|
||||||
|
private bool $autoDelete = true,
|
||||||
|
private bool $nowait = false,
|
||||||
|
private array $arguments = [],
|
||||||
|
private mixed $ticket = null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function passive(): bool
|
||||||
|
{
|
||||||
|
return $this->passive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function durable(): bool
|
||||||
|
{
|
||||||
|
return $this->durable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exclusive(): bool
|
||||||
|
{
|
||||||
|
return $this->exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autoDelete(): bool
|
||||||
|
{
|
||||||
|
return $this->autoDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nowait(): bool
|
||||||
|
{
|
||||||
|
return $this->nowait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function arguments(): array
|
||||||
|
{
|
||||||
|
return $this->arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ticket(): ?int
|
||||||
|
{
|
||||||
|
return $this->ticket;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/Service/Configuration.php
Normal file
30
src/Service/Configuration.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Configuration as ConfigurationObject;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class Configuration
|
||||||
|
{
|
||||||
|
public function get(string $connection = 'default'): ConfigurationObject
|
||||||
|
{
|
||||||
|
$config = config(sprintf('rabbitmq.connections.%s', $connection));
|
||||||
|
|
||||||
|
if (empty($config)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
sprintf('Not found rabbitmq config for connection %s', $connection)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConfigurationObject(
|
||||||
|
$config['host'],
|
||||||
|
(int) $config['port'],
|
||||||
|
$config['user'],
|
||||||
|
$config['password'],
|
||||||
|
$config['vhost'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/Service/Connector.php
Normal file
62
src/Service/Connector.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Configuration;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Connection;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||||
|
|
||||||
|
class Connector
|
||||||
|
{
|
||||||
|
public function connect(Configuration $config, Queue $queue): Connection
|
||||||
|
{
|
||||||
|
$connection = new AMQPStreamConnection(
|
||||||
|
$config->host(),
|
||||||
|
$config->port(),
|
||||||
|
$config->user(),
|
||||||
|
$config->password(),
|
||||||
|
$config->vhost()
|
||||||
|
);
|
||||||
|
|
||||||
|
$channel = $connection->channel();
|
||||||
|
|
||||||
|
$channel->queue_declare(
|
||||||
|
$queue->name(),
|
||||||
|
$queue->declaration()->passive(),
|
||||||
|
$queue->declaration()->durable(),
|
||||||
|
$queue->declaration()->exclusive(),
|
||||||
|
$queue->declaration()->autoDelete(),
|
||||||
|
$queue->declaration()->nowait(),
|
||||||
|
$queue->declaration()->arguments(),
|
||||||
|
$queue->declaration()->ticket()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $queue->exchange()->isDefault()) {
|
||||||
|
$channel->exchange_declare(
|
||||||
|
$queue->exchange()->name(),
|
||||||
|
$queue->exchange()->type(),
|
||||||
|
$queue->exchange()->declaration()->passive(),
|
||||||
|
$queue->exchange()->declaration()->durable(),
|
||||||
|
$queue->exchange()->declaration()->autoDelete(),
|
||||||
|
$queue->exchange()->declaration()->internal(),
|
||||||
|
$queue->exchange()->declaration()->nowait(),
|
||||||
|
$queue->exchange()->declaration()->arguments(),
|
||||||
|
$queue->exchange()->declaration()->ticket()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel->queue_bind(
|
||||||
|
$queue->name(),
|
||||||
|
$queue->exchange()->name(),
|
||||||
|
$queue->bindings()->routingKey(),
|
||||||
|
$queue->bindings()->nowait(),
|
||||||
|
$queue->bindings()->arguments(),
|
||||||
|
$queue->bindings()->ticket()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Connection($connection, $channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/Service/EventEmitter.php
Normal file
19
src/Service/EventEmitter.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
|
||||||
|
class EventEmitter
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Dispatcher $dispatcher
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function emit(object $event): void
|
||||||
|
{
|
||||||
|
$this->dispatcher->dispatch($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/Service/EventMapper.php
Normal file
49
src/Service/EventMapper.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Exception\AssociatedEventNotFound;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\EventMapper as EventMapperInterface;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use App\Shared\Event\Event;
|
||||||
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
|
|
||||||
|
class EventMapper implements EventMapperInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Application $app
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function map(Queue $queue, array $payload): Event
|
||||||
|
{
|
||||||
|
$map = config('rabbitmq.event.map', []);
|
||||||
|
|
||||||
|
$queueName = $queue->name();
|
||||||
|
$routingKey = $queue->bindings()->routingKey();
|
||||||
|
|
||||||
|
foreach ($map as $eventClass => $config) {
|
||||||
|
$match = null;
|
||||||
|
|
||||||
|
if (! empty($config['queues'] ?? [])) {
|
||||||
|
$match = in_array($queueName, $config['queues'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($config['routing_keys'] ?? [])) {
|
||||||
|
$match = is_null($match) ? true : $match;
|
||||||
|
$match = $match && in_array($routingKey, $config['routing_keys'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($match)) {
|
||||||
|
return $this->app->make($eventClass, $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($match) && $match) {
|
||||||
|
return $this->app->make($eventClass, $payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssociatedEventNotFound(json_encode($payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Service/Message.php
Normal file
27
src/Service/Message.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\EventMapper;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Unserializer;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Object\Queue;
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
|
class Message
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Unserializer $unserializer,
|
||||||
|
private EventMapper $mapper,
|
||||||
|
private EventEmitter $emitter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handle(Queue $queue, AMQPMessage $message): void
|
||||||
|
{
|
||||||
|
$payload = $this->unserializer->unserialize($message);
|
||||||
|
$event = $this->mapper->map($queue, $payload);
|
||||||
|
|
||||||
|
$this->emitter->emit($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Service/Serializer.php
Normal file
26
src/Service/Serializer.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Serializer as SerializerInterface;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use JsonSerializable;
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
|
class Serializer implements SerializerInterface
|
||||||
|
{
|
||||||
|
public function serialize(object $data): AMQPMessage
|
||||||
|
{
|
||||||
|
if ($data instanceof JsonSerializable) {
|
||||||
|
return new AMQPMessage(
|
||||||
|
json_encode($data->jsonSerialize())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Data should be an instance of BroadcastEvent'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Service/Unserializer.php
Normal file
16
src/Service/Unserializer.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Unserializer as UnserializerInterface;
|
||||||
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
|
class Unserializer implements UnserializerInterface
|
||||||
|
{
|
||||||
|
public function unserialize(AMQPMessage $message): array
|
||||||
|
{
|
||||||
|
return json_decode($message->getBody(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/ServiceProvider.php
Normal file
96
src/ServiceProvider.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ;
|
||||||
|
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Command\Consume;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\EventMapper as EventMapperInterface;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Serializer as SerializerInterface;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Interface\Unserializer as UnserializerInterface;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Listener\PublishEvent;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\EventMapper;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Serializer;
|
||||||
|
use Diffhead\PHP\LaravelRabbitMQ\Service\Unserializer;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
|
||||||
|
|
||||||
|
class ServiceProvider extends LaravelServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<int,string>
|
||||||
|
*/
|
||||||
|
private array $commands = [
|
||||||
|
Consume::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string,array<int,string>>
|
||||||
|
*/
|
||||||
|
private array $listeners = [
|
||||||
|
Broadcast::class => [
|
||||||
|
PublishEvent::class,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->registerServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->registerConfigsPublishment();
|
||||||
|
|
||||||
|
$this->registerCommands();
|
||||||
|
$this->registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerServices(): void
|
||||||
|
{
|
||||||
|
$this->app->bind(
|
||||||
|
SerializerInterface::class,
|
||||||
|
config('rabbitmq.message.serializer', Serializer::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->app->bind(
|
||||||
|
UnserializerInterface::class,
|
||||||
|
config('rabbitmq.message.unserializer', Unserializer::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->app->bind(
|
||||||
|
EventMapperInterface::class,
|
||||||
|
config('rabbitmq.event.mapper', EventMapper::class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerCommands(): void
|
||||||
|
{
|
||||||
|
$this->commands($this->commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerListeners(): void
|
||||||
|
{
|
||||||
|
foreach ($this->listeners as $event => $listeners) {
|
||||||
|
foreach ($listeners as $listener) {
|
||||||
|
Event::listen($event, $listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerConfigsPublishment(): void
|
||||||
|
{
|
||||||
|
$this->publishes(
|
||||||
|
[
|
||||||
|
$this->configPath('config/rabbitmq.php') => config_path('rabbitmq.php'),
|
||||||
|
],
|
||||||
|
'config'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function configPath(string $path): string
|
||||||
|
{
|
||||||
|
return sprintf('%s/../%s', __DIR__, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Trait/BroadcastEvent.php
Normal file
38
src/Trait/BroadcastEvent.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\LaravelRabbitMQ\Trait;
|
||||||
|
|
||||||
|
trait BroadcastEvent
|
||||||
|
{
|
||||||
|
public function getConnection(): string
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.connection');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueue(): string
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.queue');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExchange(): string
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.exchange');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExchangeType(): string
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.exchange_type');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExchangeIsDefault(): bool
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.exchange_is_default');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoutingKey(): string
|
||||||
|
{
|
||||||
|
return config('rabbitmq.event.defaults.routing_key');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user