├── .gitignore ├── LICENSE.md ├── README.RU.md ├── README.UZ.md ├── README.md ├── composer.json ├── config └── simple-mq.php ├── phpunit.xml ├── routes └── amqp-handlers.php ├── src ├── ActionMQ.php ├── Console │ └── Commands │ │ ├── ConsumeCommand.php │ │ ├── DefineQueueCommand.php │ │ ├── MakeHandlerCommand.php │ │ └── stubs │ │ └── handler.stub ├── ConsumeMQ.php ├── Exceptions │ ├── ConnectionNotFoundException.php │ └── DefaultConnectionNotFoundException.php ├── Facades │ ├── ActionMQ.php │ ├── ConsumeMQ.php │ └── SimpleMQ.php ├── MQ │ ├── Connection.php │ ├── ConnectionManager.php │ ├── Definition.php │ ├── Message.php │ ├── MessageBuilder.php │ └── Publisher.php ├── SimpleMQ.php └── SimpleRabbitMQServiceProvider.php └── tests ├── ExampleTest.php └── Pest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | 9 | .env 10 | .env.backup 11 | .env.production 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | composer.lock 19 | 20 | /.fleet 21 | /.idea 22 | /.vscode 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Temur Usmonaliyev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.RU.md: -------------------------------------------------------------------------------- 1 |
2 |

laravel-simple-rabbitmq

3 |
4 | 5 | > _Documentation generated with the help of ChatGPT_ 6 | 7 | Пакет для упрощенного использования RabbitMQ с поддержкой нескольких подключений, легкой публикацией сообщений и режимом 8 | потребителя. 9 | 10 | ### Основные функции 11 | 12 | - **Несколько подключений**: Легкое управление несколькими подключениями RabbitMQ в одном приложении. 13 | 14 | - **Поддержка Exchange**: Вы можете отправлять сообщения в exchange. 15 | 16 | - **Публикация сообщений**: Легко публикуйте сообщения в очереди и exchange с помощью удобного, цепочечного синтаксиса. 17 | 18 | - **Режим потребителя**: Позволяет потребителям получать и обрабатывать сообщения из очередей в режиме реального 19 | времени. 20 | 21 | - **Управление очередями и exchange в конфигурационном файле**: Вы можете зарегистрировать очереди и exchange в 22 | `config/simple-mq.php` и 23 | легко определить их с помощью команды `amqp:define-queues`. 24 | 25 |
26 |

Установка

27 |
28 | 29 | Вы можете установить пакет через Composer: 30 | 31 | ```bash 32 | composer require usmonaliyev/laravel-simple-rabbitmq 33 | ``` 34 | 35 | На следующем этапе необходимо опубликовать конфигурационные файлы и файлы регистрации действий: 36 | 37 | ```bash 38 | php artisan vendor:publish --provider="Usmonaliyev\SimpleRabbit\SimpleRabbitMQServiceProvider" 39 | ``` 40 | 41 | В результате выполнения этой команды у вас появится файл конфигурации `config/simple-mq.php` и файл регистрации 42 | `routes/amqp-handlers.php`. 43 | 44 | Файл конфигурации `config/simple-mq.php` содержит подключения к RabbitMQ с учетными данными, очередями, стандартным 45 | подключением и 46 | стандартной очередью. 47 | 48 | Следующий этап — настройка файла `.env`. 49 | 50 | ```.dotenv 51 | SIMPLE_MQ_CONNECTION= 52 | SIMPLE_MQ_QUEUE= 53 | 54 | SIMPLE_MQ_HOST= 55 | SIMPLE_MQ_PORT= 56 | SIMPLE_MQ_USERNAME= 57 | SIMPLE_MQ_PASSWORD= 58 | ``` 59 | 60 |
61 |

Использование

62 |
63 | 64 | Пакет может публиковать и потреблять сообщения. 65 | 66 | ### Публикация 67 | 68 | Вы можете опубликовать сообщение с использованием стандартного подключения и очереди: 69 | 70 | ```php 71 | setBody(['name' => 'Первый Foo']) 84 | ->handler('create-foo') 85 | ->publish(); 86 | 87 | return response()->json(['message' => 'OK']); 88 | } 89 | } 90 | ``` 91 | 92 | Также функция `exchange` публикует сообщение в exchange RabbitMq: 93 | 94 | ```php 95 | setBody(['name' => 'Первый Foo']) 110 | ->setRoutingKey('foo.bar') 111 | ->handler('create-foo') 112 | ->publish(); 113 | 114 | return response()->json(['message' => 'OK']); 115 | } 116 | } 117 | ``` 118 | 119 | Если у вас несколько подключений к RabbitMq, вы можете опубликовать сообщение с использованием метода `connection`. 120 | 121 | ```php 122 | queue('foo-queue') 137 | ->setBody(['name' => 'Первый Foo']) 138 | ->handler('create-foo') 139 | ->publish(); 140 | 141 | return response()->json(['message' => 'OK']); 142 | } 143 | } 144 | ``` 145 | 146 | ### Потребление 147 | 148 | Создайте папку `app/AMQP/Handlers` и создайте свои классы обработчиков. 149 | 150 | Например: 151 | 152 | ```php 153 | ack(); 166 | 167 | return ['ok' => true]; 168 | } 169 | } 170 | ``` 171 | 172 | Не забудьте подтвердить сообщение в конце процесса, иначе потребитель не примет следующее сообщение. 173 | 174 | Затем зарегистрируйте свой обработчик в файле `routes/amqp-handlers.php`. 175 | 176 | ```php 177 | 195 |

Контракты

196 |
197 | 198 | У нас есть группа в Telegram, вы можете присоединиться к нам. 199 | 200 |
201 |
202 | 203 | 204 | 205 |
206 |

Планы

207 |
208 | 209 | - [ ] Настройка exchange в `config/simple-mq.php` 210 | - [ ] Написание файлов `README.UZ.md` и `README.RU.md`. 211 | - [ ] Настройка тестирования. 212 | 213 |
214 |

Тестирование

215 |
216 | 217 | ```bash 218 | composer test 219 | ``` 220 | 221 |
222 |

Лицензия

223 |
224 | 225 | Лицензия [MIT](LICENSE.md). 226 | -------------------------------------------------------------------------------- /README.UZ.md: -------------------------------------------------------------------------------- 1 |
2 |

laravel-simple-rabbitmq

3 |
4 | 5 | > _Documentation generated with the help of ChatGPT_ 6 | 7 | RabbitMQ dan foydalanishni soddalashtiruvchi paket, ko‘p connectionlarni qo‘llab-quvvatlash, oson xabar yuborish va 8 | consumer rejimiga ega. 9 | 10 | ### Asosiy xususiyatlar 11 | 12 | - **Ko'p ulanishlar**: Bitta dastur ichida bir nechta RabbitMQ connectionlarni oson boshqarish imkoniyati. 13 | 14 | - **Exchange qo‘llab-quvvatlashi**: Xabarlarni exchange ga yuborishingiz mumkin. 15 | 16 | - **Xabar yuborish**: queue va exchange ga xabarlarni zanjirli sintaksis yordamida oson yuborish. 17 | 18 | - **Consumer mode**: Consumer navbatlardan real vaqtda xabarlarni olish va qayta ishlash imkoniyatini beradi. 19 | 20 | - **Queue va exchangelarni konfiguratsiya faylida boshqarish**: `config/simple-mq.php` faylida queue va exchange ni 21 | ro‘yxatdan o‘tkazib, `amqp:define-queues` buyrug‘i yordamida oson e'lon qilish. 22 | 23 |
24 |

O'rnatish

25 |
26 | 27 | Paketni Composer orqali o'rnatishingiz mumkin: 28 | 29 | ```bash 30 | composer require usmonaliyev/laravel-simple-rabbitmq 31 | ``` 32 | 33 | Keyingi qadamda konfiguratsiya va kerakli fayllarini nashr qilish kerak: 34 | 35 | ```bash 36 | php artisan vendor:publish --provider="Usmonaliyev\SimpleRabbit\SimpleRabbitMQServiceProvider" 37 | ``` 38 | 39 | Ushbu buyruq natijasida sizda `config/simple-mq.php` konfiguratsiya fayli va `routes/amqp-handlers.php` registratsiya 40 | fayli paydo bo'ladi. 41 | 42 | `config/simple-mq.php` konfiguratsiya faylida RabbitMQ ulanishlari, ularnish login va parollari, queue, default 43 | connection va default queue belgilash mumkin. 44 | 45 | Keyingi bosqich `.env` faylini sozlash. 46 | 47 | ```.dotenv 48 | SIMPLE_MQ_CONNECTION= 49 | SIMPLE_MQ_QUEUE= 50 | 51 | SIMPLE_MQ_HOST= 52 | SIMPLE_MQ_PORT= 53 | SIMPLE_MQ_USERNAME= 54 | SIMPLE_MQ_PASSWORD= 55 | ``` 56 | 57 |
58 |

Foydalanish

59 |
60 | 61 | Paket xabarlarni yuborish va consume qilish imkoniyatiga ega. 62 | 63 | ### Xabar yuborish 64 | 65 | Default connection va queue dan foydalanib xabar yuborishingiz uchun: 66 | 67 | ```php 68 | setBody(['name' => 'Birinchi Foo']) 81 | ->handler('create-foo') 82 | ->publish(); 83 | 84 | return response()->json(['message' => 'OK']); 85 | } 86 | } 87 | ``` 88 | 89 | Bundan tashqari, `exchange` funksiyasi xabarni RabbitMq exchange ga yuboradi: 90 | 91 | ```php 92 | setBody(['name' => 'Birinchi Foo']) 107 | ->setRoutingKey('foo.bar') 108 | ->handler('create-foo') 109 | ->publish(); 110 | 111 | return response()->json(['message' => 'OK']); 112 | } 113 | } 114 | ``` 115 | 116 | Agar sizda RabbitMq bilan ko‘p ulanish mavjud bo‘lsa, `connection` funksiyasidan foydalanishingiz mumkin. 117 | 118 | ```php 119 | queue('foo-queue') 134 | ->setBody(['name' => 'Birinchi Foo']) 135 | ->handler('create-foo') 136 | ->publish(); 137 | 138 | return response()->json(['message' => 'OK']); 139 | } 140 | } 141 | ``` 142 | 143 | ### Consuming 144 | 145 | `app/AMQP/Handlers` papkasini yarating va o‘z handler classingizni yarating. 146 | 147 | Masalan: 148 | 149 | ```php 150 | ack(); 163 | 164 | return ['ok' => true]; 165 | } 166 | } 167 | ``` 168 | 169 | Jarayon oxirida xabarni tasdiqlashni unutmang (Acknowledge => $message->ack()), aks holda consumer keyingi xabarni qabul 170 | qilmaydi. 171 | 172 | Keyin o'z handleringizni `routes/amqp-handlers.php` fayliga ro'yxatdan o'tkazing. 173 | 174 | ```php 175 | 193 |

Shartnomalar

194 |
195 | 196 | Bizda Telegram guruhi bor, siz qo‘shilishingiz mumkin. 197 | 198 |
199 |
200 | 201 | 202 | 203 |
204 |

Rejalar

205 |
206 | 207 | - [ ] `config/simple-mq.php` faylida exchange sozlash 208 | - [ ] `README.UZ.md` va `README.RU.md` fayllarini yozish. 209 | - [ ] Testlarni sozlash. 210 | 211 |
212 |

Test qilish

213 |
214 | 215 | ```bash 216 | composer test 217 | ``` 218 | 219 |
220 |

Litsenziya

221 |
222 | 223 | [MIT](LICENSE.md) Litsenziya. 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

laravel-simple-rabbitmq

3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 |
11 | 12 | The package for simplified RabbitMQ usage, supporting multiple connections, easy publishing, and consumer mode. 13 | 14 | ## Documentation 15 | 16 | - [Русский (RU)](README.RU.md) 17 | - [O'zbekcha (UZ)](README.UZ.md) 18 | 19 | ### Key Features 20 | 21 | - **Multiple Connections**: Effortlessly manage multiple RabbitMQ connections within the same application. 22 | 23 | - **Exchange supporting**: You can push messages to exchanges 24 | 25 | - **Message Publishing**: Easily publish messages to queues and exchange with a fluent, chainable syntax. 26 | 27 | - **Consumer Mode**: Enable consumers to receive and process messages from queues in real time. 28 | 29 | - **Manage queues and exchanges in config file**: You can register queues and exchanges in `config/simple-mq.php` and 30 | define them in easy way which is `amqp:define-queues` command. 31 | 32 |
33 |

Installation

34 |
35 | 36 | You can install the package via composer: 37 | 38 | ```bash 39 | composer require usmonaliyev/laravel-simple-rabbitmq 40 | ``` 41 | 42 | Next step you must publish config and action register files: 43 | 44 | ```bash 45 | php artisan vendor:publish --provider="Usmonaliyev\SimpleRabbit\SimpleRabbitMQServiceProvider" 46 | ``` 47 | 48 | As a result of this command, you will have a configuration file `config/simple-mq.php` and a registry file 49 | `routes/amqp-handlers.php`. 50 | 51 | The `config/simple-mq.php` config file contains RabbitMQ connections with credentials, queues, default connection and 52 | default queue. 53 | 54 | The next stage is configure `.env` file. 55 | 56 | ```.dotenv 57 | SIMPLE_MQ_CONNECTION= 58 | SIMPLE_MQ_QUEUE= 59 | 60 | SIMPLE_MQ_HOST= 61 | SIMPLE_MQ_PORT= 62 | SIMPLE_MQ_USERNAME= 63 | SIMPLE_MQ_PASSWORD= 64 | ``` 65 | 66 |
67 |

Usage

68 |
69 | 70 | The package can publish and consume messages 71 | 72 | ### Publishing 73 | 74 | You can publish a message with default connection and default queue: 75 | 76 | ```php 77 | setBody(['name' => 'First Foo']) 90 | ->handler('create-foo') 91 | ->publish(); 92 | 93 | return response()->json(['message' => 'OK']); 94 | } 95 | } 96 | ``` 97 | 98 | Also, `exchange` function publish message to RabbitMq exchange: 99 | 100 | ```php 101 | setBody(['name' => 'First Foo']) 116 | ->setRoutingKey('foo.bar') 117 | ->handler('create-foo') 118 | ->publish(); 119 | 120 | return response()->json(['message' => 'OK']); 121 | } 122 | } 123 | ``` 124 | 125 | If you have multiply connection to RabbitMq, you can publish a message with `connection` method. 126 | 127 | ```php 128 | queue('foo-queue') 143 | ->setBody(['name' => 'First Foo']) 144 | ->handler('create-foo') 145 | ->publish(); 146 | 147 | return response()->json(['message' => 'OK']); 148 | } 149 | } 150 | ``` 151 | 152 | ### Consuming 153 | 154 | Create `app/AMQP/Handlers` folder and create your handler classes. 155 | 156 | For example: 157 | 158 | ```php 159 | ack(); 172 | 173 | return ['ok' => true]; 174 | } 175 | } 176 | ``` 177 | 178 | Don't forget acknowledge message end of process, else consumer does not accept next message. 179 | 180 | Then register your handler in `routes/amqp-handlers.php` file. 181 | 182 | ```php 183 | 201 |

Contracts

202 |
203 | 204 | We have a telegram group, you can join us. 205 | 206 |
207 |
208 | 209 | 210 | 211 |
212 |

Plans

213 |
214 | 215 | - [ ] Exchange configuration in `config/simple-mq.php` 216 | - [ ] Setup testing. 217 | 218 |
219 |

Testing

220 |
221 | 222 | ```bash 223 | composer test 224 | ``` 225 | 226 |
227 |

License

228 |
229 | 230 | The [MIT](LICENSE.md) License. 231 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usmonaliyev/laravel-simple-rabbitmq", 3 | "description": "This package provides simple usage of rabbitmq.", 4 | "keywords": [ 5 | "usmonaliyev", 6 | "simple", 7 | "rabbit", 8 | "mq" 9 | ], 10 | "homepage": "https://github.com/usmonaliyev/laravel-simple-rabbitmq", 11 | "license": "MIT", 12 | "type": "library", 13 | "authors": [ 14 | { 15 | "name": "Temur Usmonaliyev", 16 | "email": "t.usmonaliyev99@gmail.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.1", 22 | "illuminate/console": "*", 23 | "illuminate/support": "*", 24 | "php-amqplib/php-amqplib": "^3.7" 25 | }, 26 | "require-dev": { 27 | "laravel/pint": "^1.18", 28 | "pestphp/pest": "^1.23", 29 | "phpunit/phpunit": "^9.0", 30 | "symfony/var-dumper": "^6.4" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Usmonaliyev\\SimpleRabbit\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Usmonaliyev\\SimpleRabbit\\Tests\\": "tests" 40 | } 41 | }, 42 | "minimum-stability": "stable", 43 | "scripts": { 44 | "test": "vendor/bin/pest", 45 | "test-coverage": "vendor/bin/pest --coverage-html coverage" 46 | }, 47 | "config": { 48 | "sort-packages": true, 49 | "allow-plugins": { 50 | "pestphp/pest-plugin": true 51 | } 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "Usmonaliyev\\SimpleRabbit\\SimpleRabbitMQServiceProvider" 57 | ] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/simple-mq.php: -------------------------------------------------------------------------------- 1 | env('SIMPLE_MQ_QUEUE', 'simple'), 16 | 17 | 'connection' => env('SIMPLE_MQ_CONNECTION', 'default'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Connections 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may define all of the message broker connections for your 25 | | application. These connections can be reused across multiple queues. 26 | | 27 | */ 28 | 'connections' => [ 29 | 30 | 'default' => [ 31 | 'host' => env('SIMPLE_MQ_HOST', 'localhost'), 32 | 'port' => env('SIMPLE_MQ_PORT', 5672), 33 | 'username' => env('SIMPLE_MQ_USERNAME', 'guest'), 34 | 'password' => env('SIMPLE_MQ_PASSWORD', 'guest'), 35 | 'vhost' => env('SIMPLE_MQ_VHOST', '/'), 36 | 'insist' => false, 37 | 'login_method' => 'AMQPLAIN', 38 | 'login_response' => null, 39 | 'locale' => 'en_US', 40 | 'connection_timeout' => 30, 41 | 'read_write_timeout' => 30, 42 | 'context' => null, 43 | 'keepalive' => false, 44 | 'heartbeat' => false, 45 | 'channel_rpc_timeout' => 0, 46 | 'ssl_protocol' => null, 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Queues 53 | |-------------------------------------------------------------------------- 54 | | 55 | | Each queue can be linked to a specific connection and configured with 56 | | additional queue-specific settings, such as queue name or routing keys. 57 | | 58 | */ 59 | 'queues' => [ 60 | 61 | 'simple' => [ 62 | // Specifies the name of the connection to be used for this queue. 63 | 'connection' => 'default', 64 | 65 | /** 66 | * Be carefully these arguments because you can define queue one time with these arguments. 67 | * After queue is created, you don't change these arguments 68 | * 69 | * If you must change arguments, delete queue and define them one more time. 70 | */ 71 | 'arguments' => [ 72 | /** 73 | * Sets the time-to-live for messages in this queue to 60000 milliseconds (60 seconds). 74 | * 75 | * 1 min = 60000 76 | * 1 hour = 3600000 77 | * 1 day = 3600000 * 24 78 | */ 79 | 'x-message-ttl' => 3600000, 80 | // Limits the maximum number of messages in the queue to 1000. 81 | 'x-max-length' => 1000, 82 | // Specifies the dead-letter exchange to which messages will be sent if they expire. 83 | 'x-dead-letter-exchange' => null, 84 | // Routing key for messages that are routed to the dead-letter exchange. 85 | 'x-dead-letter-routing-key' => null, 86 | ], 87 | ], 88 | ], 89 | 90 | ]; 91 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | ./tests 11 | 12 | 13 | 14 | 15 | ./app 16 | ./src 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /routes/amqp-handlers.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | private array $handlers = []; 18 | 19 | /** 20 | * Register a new action 21 | */ 22 | public function register(string $handler, array|callable $callback): void 23 | { 24 | $this->handlers[$handler] = $callback; 25 | } 26 | 27 | /** 28 | * Getter of actions property 29 | * 30 | * @return array[]|callable[] 31 | */ 32 | public function getHandlers(): array 33 | { 34 | return $this->handlers; 35 | } 36 | 37 | /** 38 | * Loading actions from route/amqp-handlers.php 39 | */ 40 | public function load(): void 41 | { 42 | $callback = fn () => include_once base_path('routes/amqp-handlers.php'); 43 | 44 | $callback(); 45 | } 46 | 47 | /** 48 | * Main consumer 49 | */ 50 | public function consume(AMQPMessage $amqpMessage): mixed 51 | { 52 | $message = new Message($amqpMessage); 53 | 54 | // If there is no handler which match to message, message is deleted 55 | if (! isset($this->handlers[$message->getHandler()])) { 56 | $message->ack(); 57 | 58 | return null; 59 | } 60 | 61 | return $this->dispatch($message); 62 | } 63 | 64 | /** 65 | * Dispatcher to execute handler 66 | */ 67 | protected function dispatch($message): mixed 68 | { 69 | $handler = $this->handlers[$message->getHandler()]; 70 | 71 | try { 72 | if (is_callable($handler)) { 73 | return call_user_func_array($handler, [$message]); 74 | } 75 | 76 | [$class, $method] = $handler; 77 | $instance = App::make($class); 78 | 79 | return $instance->{$method}($message); 80 | 81 | } catch (Exception $e) { 82 | $error = sprintf('ERROR [%s] %s: %s'.PHP_EOL, gmdate('Y-m-d H:i:s'), get_class($e), $e->getMessage()); 83 | echo $error; 84 | 85 | return null; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Console/Commands/ConsumeCommand.php: -------------------------------------------------------------------------------- 1 | argument('queue'); 35 | $connection = $this->argument('connection'); 36 | 37 | $action = App::make(ActionMQ::class); 38 | $action->load(); 39 | 40 | ConsumeMQ::connection($connection) 41 | ->queue($queue) 42 | ->listen($action->consume(...)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Console/Commands/DefineQueueCommand.php: -------------------------------------------------------------------------------- 1 | $queue) { 33 | $arguments = array_filter($queue['arguments'], fn ($argument) => ! is_null($argument)); 34 | 35 | SimpleMQ::connection($queue['connection']) 36 | ->getDefinition() 37 | ->setArguments($arguments) 38 | ->defineQueue($name); 39 | } 40 | 41 | $this->info('Queues are defined.'); 42 | 43 | $exchange = $this->option('exchange'); 44 | if ($exchange) { 45 | return; 46 | } 47 | 48 | // TODO Define exchange 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/Commands/MakeHandlerCommand.php: -------------------------------------------------------------------------------- 1 | ack(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/ConsumeMQ.php: -------------------------------------------------------------------------------- 1 | connection = $name; 31 | 32 | return $this; 33 | } 34 | 35 | public function queue(?string $name = null): self 36 | { 37 | $this->queue = $name; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Start listening RabbitMq server 44 | * 45 | * @throws Exception 46 | */ 47 | public function listen(callable $callback): void 48 | { 49 | $this->buildConnection(); 50 | $this->setupQos(); 51 | 52 | $queue = $this->getQueue(); 53 | 54 | $this->channel->basic_consume($queue, '', false, false, false, false, $callback); 55 | 56 | echo "Consumer is listening to \"$queue\" queue...".PHP_EOL; 57 | 58 | while ($this->channel->is_consuming()) { 59 | $this->channel->wait(); 60 | } 61 | 62 | $this->channel->close(); 63 | $this->amqpConnection->close(); 64 | } 65 | 66 | /** 67 | * Setup QOS for this channel to read message one by one 68 | */ 69 | protected function setupQos($qos = [null, 1, null]): void 70 | { 71 | $this->channel->basic_qos(...$qos); 72 | } 73 | 74 | /** 75 | * Building AMQPStreamConnection to rabbit mq server 76 | * 77 | * @throws Exception 78 | */ 79 | protected function buildConnection(): void 80 | { 81 | $manager = App::make(ConnectionManager::class); 82 | 83 | $this->amqpConnection = $manager 84 | ->connection($this->getConnectionName()) 85 | ->getAMQPConnection(); 86 | 87 | $this->channel = $this->amqpConnection->channel(); 88 | } 89 | 90 | /** 91 | * Getting queue name if it is null, default queue name 92 | */ 93 | public function getQueue(): string 94 | { 95 | return $this->queue ??= Config::get('simple-mq.queue'); 96 | } 97 | 98 | /** 99 | * Getting connection name if it is null, default connection name 100 | */ 101 | public function getConnectionName(): string 102 | { 103 | return $this->connection ??= Config::get('simple-mq.connection'); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Exceptions/ConnectionNotFoundException.php: -------------------------------------------------------------------------------- 1 | name = $name; 28 | 29 | $config = Config::get("simple-mq.connections.$name"); 30 | 31 | if (! isset($config)) { 32 | throw new ConnectionNotFoundException($name); 33 | } 34 | 35 | $this->connection = new AMQPStreamConnection( 36 | $config['host'], 37 | $config['port'], 38 | $config['username'], 39 | $config['password'], 40 | $config['vhost'], 41 | $config['insist'], 42 | $config['login_method'], 43 | $config['login_response'], 44 | $config['locale'], 45 | $config['connection_timeout'], 46 | $config['read_write_timeout'], 47 | $config['context'], 48 | $config['keepalive'], 49 | $config['heartbeat'], 50 | $config['channel_rpc_timeout'], 51 | $config['ssl_protocol'] 52 | ); 53 | } 54 | 55 | /** 56 | * Get a publisher for the specified queue. 57 | */ 58 | public function queue(?string $queueName = null): MessageBuilder 59 | { 60 | $queueName = $queueName ?? Config::get('simple-mq.queue'); 61 | 62 | return new MessageBuilder($this->name, $queueName, 'QUEUE'); 63 | } 64 | 65 | /** 66 | * Get a publisher for the specified exchange. 67 | */ 68 | public function exchange($exchangeName): MessageBuilder 69 | { 70 | return new MessageBuilder($this->name, $exchangeName, 'EXCHANGE'); 71 | } 72 | 73 | /** 74 | * Returns the active AMQPStreamConnection connection. 75 | * 76 | * @throws Exception 77 | */ 78 | public function getAMQPConnection(): AMQPStreamConnection 79 | { 80 | return $this->connection; 81 | } 82 | 83 | /** 84 | * Retrieves an AMQP channel for the current connection. 85 | * If no channel exists, a new channel is created and returned. 86 | * Channels are used to interact with RabbitMQ, allowing you to 87 | * publish messages, consume messages, and perform other operations. 88 | * 89 | * @return AMQPChannel The channel associated with the current connection. 90 | * 91 | * @throws Exception If the channel cannot be created or the connection is invalid. 92 | */ 93 | public function getChannel(): AMQPChannel 94 | { 95 | return $this->connection->channel(); 96 | } 97 | 98 | /** 99 | * Definition section manager queues and exchange 100 | */ 101 | public function getDefinition(): Definition 102 | { 103 | return new Definition($this->connection); 104 | } 105 | 106 | /** 107 | * Close channel and connection before destruction 108 | * 109 | * @throws Exception 110 | */ 111 | public function __destruct() 112 | { 113 | $this->connection->close(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/MQ/ConnectionManager.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | private array $connections = []; 15 | 16 | /** 17 | * Get a connection instance. 18 | * 19 | * @throws Exception Default queue is not configured 20 | */ 21 | public function connection(?string $name = null): Connection 22 | { 23 | $name = $name ?? Config::get('simple-mq.connection'); 24 | 25 | if (! $name) { 26 | throw new DefaultConnectionNotFoundException; 27 | } 28 | 29 | return $this->connections[$name] ??= $this->make($name); 30 | } 31 | 32 | /** 33 | * Create a new AMQPStreamConnection instance. 34 | * 35 | * @throws Exception 36 | */ 37 | protected function make(string $name): Connection 38 | { 39 | return new Connection($name); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/MQ/Definition.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 49 | 50 | $this->channel = $connection->channel(); 51 | } 52 | 53 | /** 54 | * Define a new queue 55 | */ 56 | public function defineQueue(string $name): ?array 57 | { 58 | $argTable = $this->getArguments(); 59 | 60 | return $this->channel->queue_declare( 61 | $name, 62 | $this->passive, 63 | $this->durable, 64 | $this->exclusive, 65 | $this->auto_delete, 66 | $this->nowait, 67 | $argTable, 68 | ); 69 | } 70 | 71 | /** 72 | * To define new exchange 73 | * 74 | * @throws Exception Now there is no implementation 75 | */ 76 | public function defineExchange(string $name, $type = 'direct'): ?array 77 | { 78 | throw new Exception('The function does not have implementation.'); 79 | } 80 | 81 | /** 82 | * Setup arguments to create new queue 83 | * 84 | * @return $this 85 | */ 86 | public function setArguments(array $arguments): self 87 | { 88 | $this->arguments = $arguments; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Creates and returns AMQPTable to define new queue 95 | */ 96 | protected function getArguments(): AMQPTable 97 | { 98 | return new AMQPTable($this->arguments); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/MQ/Message.php: -------------------------------------------------------------------------------- 1 | message = $message; 19 | 20 | $this->separator(); 21 | } 22 | 23 | /** 24 | * Content-Type of AMQPMessage 25 | */ 26 | protected string $contentType = 'application/json'; 27 | 28 | /** 29 | * Content-Encoding of AMQPMessage 30 | */ 31 | protected string $contentEncoding = 'utf-8'; 32 | 33 | /** 34 | * Application headers of AMQPMessage 35 | */ 36 | protected array $applicationHeaders; 37 | 38 | /** 39 | * Message priority default is 0 40 | */ 41 | protected int $priority; 42 | 43 | /** 44 | * Timestamp of AMQPMessage 45 | * 46 | * @var int 47 | */ 48 | protected mixed $timestamp; 49 | 50 | /** 51 | * Handler name which accepted from AMQPMessage 52 | */ 53 | protected string $handler; 54 | 55 | /** 56 | * Separator message details from AMQPMessage 57 | */ 58 | protected function separator(): void 59 | { 60 | $this->body = json_decode($this->message->getBody(), true); 61 | 62 | /** 63 | * @var array $properties 64 | */ 65 | $properties = $this->message->get_properties(); 66 | 67 | $headers = $properties['application_headers']->getNativeData(); 68 | 69 | $this->handler = $headers['handler'] ?? ''; 70 | $this->applicationHeaders = $headers; 71 | 72 | $this->contentType = $properties['content_type'] ?? 'application/json'; 73 | $this->contentEncoding = $properties['content_encoding'] ?? 'utf-8'; 74 | $this->priority = $properties['priority'] ?? 0; 75 | $this->timestamp = $properties['timestamp'] ?? 0; 76 | } 77 | 78 | /** 79 | * Body of AMQPMessage as array 80 | */ 81 | protected ?array $body; 82 | 83 | /** 84 | * Acknowledge one or more messages. 85 | */ 86 | public function ack(): void 87 | { 88 | $this->message->ack(); 89 | } 90 | 91 | /** 92 | * Get the instance as an array. 93 | */ 94 | public function toArray(): array 95 | { 96 | return [ 97 | 'headers' => $this->applicationHeaders, 98 | 'body' => $this->body, 99 | 'timestamp' => $this->timestamp, 100 | ]; 101 | } 102 | 103 | /** 104 | * Returns value of application_headers in AMQPMessage 105 | */ 106 | public function getApplicationHeaders(): array 107 | { 108 | return $this->applicationHeaders; 109 | } 110 | 111 | /** 112 | * Returns handler name of AMQPMessage 113 | */ 114 | public function getHandler(): string 115 | { 116 | return $this->handler; 117 | } 118 | 119 | /** 120 | * Returns body of AMQPMessage as array 121 | */ 122 | public function getBody(): ?array 123 | { 124 | return $this->body; 125 | } 126 | 127 | /** 128 | * Dump this class 129 | */ 130 | public function dd(): void 131 | { 132 | dump($this); 133 | } 134 | 135 | /** 136 | * Get body as json 137 | */ 138 | public function bodyAsJson(): bool|string 139 | { 140 | return json_encode($this->body); 141 | } 142 | 143 | /** 144 | * Print message to log 145 | */ 146 | public function log($ack = true): void 147 | { 148 | $body = $this->bodyAsJson(); 149 | 150 | $ack && $this->ack(); 151 | 152 | echo sprintf('%s | %s', date('Y-m-d H:i:s'), $body).PHP_EOL; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/MQ/MessageBuilder.php: -------------------------------------------------------------------------------- 1 | 'application/json', 35 | 'content_encoding' => 'utf-8', 36 | 'application_headers' => [], 37 | 'priority' => 0, 38 | 'correlation_id' => null, 39 | 'reply_to' => null, 40 | 'timestamp' => null, 41 | ]; 42 | 43 | public function __construct(string $connection, string $to, string $type) 44 | { 45 | $this->connection = $connection; 46 | $this->to = $to; 47 | $this->type = $type; 48 | 49 | $this->properties['timestamp'] = time(); 50 | } 51 | 52 | /** 53 | * Creates a Publisher instance to handle specific actions. 54 | */ 55 | public function handler(string $name): Publisher 56 | { 57 | $this->addHeader('handler', $name); 58 | 59 | $message = $this->getMessage(); 60 | 61 | return new Publisher($message, $this->connection, $this->type, $this->to, $this->routingKey); 62 | } 63 | 64 | /** 65 | * Get the AMQPMessage instance 66 | */ 67 | private function getMessage(): AMQPMessage 68 | { 69 | $body = $this->getBodyAsJson(); 70 | $properties = $this->toProperties(); 71 | 72 | return new AMQPMessage($body, $properties); 73 | } 74 | 75 | /** 76 | * Set headers for the message 77 | */ 78 | public function setHeaders(array $headers): self 79 | { 80 | $this->properties['application_headers'] = $headers; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Add key value pair to existing headers 87 | */ 88 | public function addHeader($key, $value): self 89 | { 90 | $this->properties['application_headers'][$key] = $value; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Set the message body 97 | * 98 | * Stores the body data and invalidates the cached AMQPMessage. 99 | */ 100 | public function setBody(array $body): self 101 | { 102 | $this->body = $body; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Get the body as a JSON string 109 | */ 110 | protected function getBodyAsJson(): string 111 | { 112 | return json_encode($this->body); 113 | } 114 | 115 | protected function toProperties(): array 116 | { 117 | $headers = $this->buildHeaders(); 118 | $this->properties['application_headers'] = $headers; 119 | 120 | return $this->properties; 121 | } 122 | 123 | /** 124 | * Builds and returns message header 125 | */ 126 | private function buildHeaders(): AMQPTable 127 | { 128 | return new AMQPTable($this->properties['application_headers']); 129 | } 130 | 131 | public function setRoutingKey(string $routingKey): self 132 | { 133 | $this->routingKey = $routingKey; 134 | 135 | return $this; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/MQ/Publisher.php: -------------------------------------------------------------------------------- 1 | message = $message; 36 | $this->connection = $connection; 37 | $this->type = $type; 38 | $this->to = $to; 39 | $this->routingKey = $routingKey; 40 | } 41 | 42 | /** 43 | * Publish the message 44 | */ 45 | public function publish(): void 46 | { 47 | $this->{$this->type}(); 48 | } 49 | 50 | /** 51 | * Handle publishing to a Queue. 52 | * 53 | * @throws Exception 54 | */ 55 | private function QUEUE(): void 56 | { 57 | $connection = $this->getConnection(); 58 | $channel = $connection->getChannel(); 59 | 60 | $channel->basic_publish($this->message, '', $this->to); 61 | } 62 | 63 | /** 64 | * Handle publishing to an Exchange. 65 | * 66 | * @throws Exception 67 | */ 68 | private function EXCHANGE(): void 69 | { 70 | $connection = $this->getConnection(); 71 | $channel = $connection->getChannel(); 72 | 73 | $channel->basic_publish($this->message, $this->to, $this->routingKey); 74 | } 75 | 76 | /** 77 | * Retrieve the active RabbitMQ connection. 78 | * 79 | * @throws Exception if connection cannot be established. 80 | */ 81 | private function getConnection(): Connection 82 | { 83 | $manager = App::make(ConnectionManager::class); 84 | 85 | return $manager->connection($this->connection); 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/SimpleMQ.php: -------------------------------------------------------------------------------- 1 | getManager()->connection($name); 20 | } 21 | 22 | /** 23 | * Retrieve a queue publisher for the specified queue. 24 | * 25 | * @throws Exception 26 | */ 27 | public function queue(?string $queueName = null): MQ\MessageBuilder 28 | { 29 | return $this->connection()->queue($queueName); 30 | } 31 | 32 | /** 33 | * Retrieve an exchange publisher for the specified exchange. 34 | * 35 | * @throws Exception 36 | */ 37 | public function exchange(string $exchangeName): MQ\MessageBuilder 38 | { 39 | return $this->connection()->exchange($exchangeName); 40 | } 41 | 42 | /** 43 | * Retrieve the ConnectionManager instance. 44 | * 45 | * @return ConnectionManager The connection manager. 46 | */ 47 | protected function getManager(): ConnectionManager 48 | { 49 | // Resolve ConnectionManager only once and reuse it for consistency. 50 | return App::make(ConnectionManager::class); 51 | } 52 | 53 | /** 54 | * Set a message body directly with default connection and default queue 55 | * 56 | * @throws Exception 57 | */ 58 | public function setBody(array $body): MQ\MessageBuilder 59 | { 60 | return $this->connection()->queue()->setBody($body); 61 | } 62 | 63 | /** 64 | * Set a message headers with default connection and default queue 65 | * 66 | * @throws Exception 67 | */ 68 | public function setHeaders(array $headers): MQ\MessageBuilder 69 | { 70 | return $this->connection()->queue()->setHeaders($headers); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/SimpleRabbitMQServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 19 | $this->publishes([__DIR__.'/../config/simple-mq.php' => config_path('simple-mq.php')], 'simple-mq'); 20 | 21 | $this->publishes([__DIR__.'/../routes/amqp-handlers.php' => base_path('routes/amqp-handlers.php')]); 22 | 23 | $this->commands([ 24 | DefineQueueCommand::class, 25 | ConsumeCommand::class, 26 | MakeHandlerCommand::class, 27 | ]); 28 | } 29 | } 30 | 31 | /** 32 | * Register the application services. 33 | */ 34 | public function register(): void 35 | { 36 | // Automatically apply the package configuration 37 | $this->mergeConfigFrom(__DIR__.'/../config/simple-mq.php', 'simple-mq'); 38 | 39 | $this->app->singleton('simple-rabbitmq', fn () => new SimpleMQ); 40 | 41 | $this->app->singleton(ActionMQ::class, fn () => new ActionMQ); 42 | $this->app->singleton(ConnectionManager::class, fn () => new ConnectionManager); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 15 | uses(\PHPUnit\Framework\TestCase::class)->in('Unit'); 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Expectations 20 | |-------------------------------------------------------------------------- 21 | | 22 | | When you're writing tests, you often need to check that values meet certain conditions. The 23 | | "expect()" function gives you access to a set of "expectations" methods that you can use 24 | | to assert different things. Of course, you may extend the Expectation API at any time. 25 | | 26 | */ 27 | 28 | expect()->extend('toBeOne', function () { 29 | return $this->toBe(1); 30 | }); 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Functions 35 | |-------------------------------------------------------------------------- 36 | | 37 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 38 | | project that you don't want to repeat in every file. Here you can also expose helpers as 39 | | global functions to help you to reduce the number of lines of code in your test files. 40 | | 41 | */ 42 | 43 | function something() 44 | { 45 | // .. 46 | } 47 | --------------------------------------------------------------------------------