├── .docker
├── php.ini
├── Dockerfile
└── Makefile
├── docs
├── php-simple-queue-941x320.gif
└── guide
│ ├── README.md
│ ├── install.md
│ ├── configuration.md
│ ├── cookbook.md
│ ├── example.md
│ ├── transport.md
│ ├── producer.md
│ └── consuming.md
├── .gitignore
├── src
├── ConfigException.php
├── QueueException.php
├── Transport
│ ├── TransportException.php
│ ├── TransportInterface.php
│ ├── DoctrineDbalTableCreator.php
│ └── DoctrineDbalTransport.php
├── Serializer
│ ├── SerializerInterface.php
│ ├── BaseSerializer.php
│ └── SymfonySerializer.php
├── Job.php
├── Context.php
├── Priority.php
├── Status.php
├── Producer.php
├── MessageHydrator.php
├── Message.php
├── Config.php
└── Consumer.php
├── .scrutinizer.yml
├── .travis.yml
├── example
├── produce.php
├── consume.php
└── advanced-consume.php
├── phpunit.xml
├── tests
├── JobTest.php
├── ContextTest.php
├── Helper
│ ├── MockSchemaManager.php
│ ├── DBALDriverResult.php
│ └── MockConnection.php
├── Serializer
│ └── SerializerTest.php
├── MessageStatusTest.php
├── MessagePriorityTest.php
├── Transport
│ ├── QueueTableCreatorTest.php
│ └── DoctrineDbalTransportTest.php
├── ConsumerTest.php
├── MessageTest.php
├── MessageHydratorTest.php
├── ConfigTest.php
└── ProducerTest.php
├── LICENSE
├── .php_cs.php
├── composer.json
├── CHANGELOG.md
└── README.md
/.docker/php.ini:
--------------------------------------------------------------------------------
1 | [PHP]
2 | xdebug.mode=coverage
--------------------------------------------------------------------------------
/docs/php-simple-queue-941x320.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nepster-web/php-simple-queue/HEAD/docs/php-simple-queue-941x320.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /composer.lock
3 | /phpcs.xml
4 | /vendor/
5 | /.phpunit.result.cache
6 | /.php_cs.cache
7 | /docs/tmp/
8 | /.coverage.clover
--------------------------------------------------------------------------------
/src/ConfigException.php:
--------------------------------------------------------------------------------
1 |
21 |
22 | [Go back](https://github.com/nepster-web/php-simple-queue)
--------------------------------------------------------------------------------
/example/produce.php:
--------------------------------------------------------------------------------
1 | 'pdo_sqlite',
9 | 'path' => '/db/queue.db',
10 | ]);
11 |
12 | $transport = new \Simple\Queue\Transport\DoctrineDbalTransport($connection);
13 |
14 | $producer = new \Simple\Queue\Producer($transport, null);
15 |
16 |
17 | echo 'Start send to queue' . PHP_EOL;
18 |
19 | while (true) {
20 |
21 | $message = $producer->createMessage('my_queue', ['id' => uniqid('', true)]);
22 |
23 | $producer->send($message);
24 |
25 | echo sprintf('Sent message: %s ', $message->getBody());
26 |
27 | echo PHP_EOL;
28 |
29 | sleep(1);
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 | ## :page_facing_up: Install
21 |
22 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/):
23 |
24 | Either run
25 |
26 | ```
27 | php composer.phar require --prefer-dist nepster-web/php-simple-queue
28 | ```
29 |
30 | or add
31 |
32 | ```
33 | "nepster-web/php-simple-queue": "*"
34 | ```
35 |
36 |
37 |
38 | > TIP: When install this package, specify the exact version. For example: `"nepster-web/php-simple-queue": "1.0.0"`
39 |
40 | If you specify the exact version of this library it is possible to avoid problems with updating and breaking backward compatibility.
--------------------------------------------------------------------------------
/src/Serializer/SymfonySerializer.php:
--------------------------------------------------------------------------------
1 | serializer = new Serializer([], [new JsonEncoder()]);
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function serialize($data): string
31 | {
32 | return $this->serializer->encode($data, JsonEncoder::FORMAT);
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function deserialize(string $data)
39 | {
40 | return $this->serializer->decode($data, JsonEncoder::FORMAT);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Anatolyi Razumovskyi
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.
22 |
--------------------------------------------------------------------------------
/docs/guide/configuration.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Usage basics
2 | =============================
3 |
4 | Configuration.
5 |
6 |
7 | ## :book: Guide
8 |
9 | * [Guide](./README.md)
10 | * [Install](./install.md)
11 | * [Transport](./transport.md)
12 | * **[Configuration](./configuration.md)**
13 | * [Producer (Send message)](./producer.md)
14 | * [Consuming](./consuming.md)
15 | * [Example](./example.md)
16 | * [Cookbook](./cookbook.md)
17 |
18 |
19 |
20 | ## :page_facing_up: Configuration
21 |
22 | You need to use the same config for producer and consumer.
23 |
24 |
25 |
26 | **Create example config:**
27 |
28 | ```php
29 | $config = \Simple\Queue\Config::getDefault()
30 | ->changeRedeliveryTimeInSeconds(100)
31 | ->changeNumberOfAttemptsBeforeFailure(3)
32 | ->withSerializer(new \Simple\Queue\Serializer\BaseSerializer())
33 | ->registerJob(MyJob::class, new MyJob())
34 | ->registerProcessor('my_queue', static function(\Simple\Queue\Message $message, \Simple\Queue\Producer $producer): string {
35 |
36 | // Your message handling logic
37 |
38 | return \Simple\Queue\Consumer::STATUS_ACK;
39 | });
40 | ```
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/Transport/TransportInterface.php:
--------------------------------------------------------------------------------
1 | producer = $producer;
31 | $this->data = $data;
32 | $this->message = $message;
33 | }
34 |
35 | /**
36 | * @return Producer
37 | */
38 | public function getProducer(): Producer
39 | {
40 | return $this->producer;
41 | }
42 |
43 | /**
44 | * @return Message
45 | */
46 | public function getMessage(): Message
47 | {
48 | return $this->message;
49 | }
50 |
51 | /**
52 | * @return array
53 | */
54 | public function getData(): array
55 | {
56 | return $this->data;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/ContextTest.php:
--------------------------------------------------------------------------------
1 | createMessage('my_queue', '');
35 |
36 | $context = new Context($producer, $message, []);
37 |
38 | self::assertEquals($producer, $context->getProducer());
39 | self::assertEquals($message, $context->getMessage());
40 | self::assertEquals([], $context->getData());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.docker/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_NAME = php-simple-queue
2 |
3 | USER_ID = $(shell id -u)
4 | GROUP_ID=$(shell id -g)
5 | APP_DIR="${PWD}/.."
6 |
7 | app_run := docker exec -it --user="${USER_ID}" $(PROJECT_NAME)
8 |
9 | .PHONY : help build start stop restart composer php test test-coverage
10 |
11 | .DEFAULT_GOAL := help
12 |
13 | help: ## show this help
14 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
15 |
16 | build: ## build application
17 | docker build --build-arg USER_ID=${USER_ID} --build-arg GROUP_ID=${GROUP_ID} --no-cache --tag ${PROJECT_NAME} .
18 |
19 | start: ## start application (in background)
20 | docker run -d --name=${PROJECT_NAME} -v ${APP_DIR}:/app ${PROJECT_NAME}
21 | make composer cmd=install
22 |
23 | stop: ## stop all containers
24 | docker stop ${PROJECT_NAME} && docker rm ${PROJECT_NAME}
25 |
26 | restart: ## restart all containers
27 | make stop || true
28 | make start
29 |
30 | composer: ## run composer
31 | ifneq ($(cmd),)
32 | $(app_run) sh -c "composer $(cmd)"
33 | else
34 | $(app_run) sh -c "composer update"
35 | endif
36 |
37 | php: ## run php
38 | ifneq ($(cmd),)
39 | $(app_run) sh -c "php $(cmd)"
40 | else
41 | $(app_run) sh -c "php"
42 | endif
43 |
--------------------------------------------------------------------------------
/tests/Helper/MockSchemaManager.php:
--------------------------------------------------------------------------------
1 | in(['src', 'tests']);
5 |
6 | $config = new PhpCsFixer\Config();
7 | return $config
8 | ->setRules([
9 | '@PSR2' => true,
10 | 'no_empty_phpdoc' => true,
11 | 'single_blank_line_before_namespace' => true,
12 | 'array_syntax' => ['syntax' => 'short'],
13 | 'ordered_imports' => ['sortAlgorithm' => 'length'],
14 | 'no_spaces_after_function_name' => true,
15 | 'no_whitespace_in_blank_line' => true,
16 | 'no_whitespace_before_comma_in_array' => true,
17 | 'no_useless_return' => true,
18 | 'no_useless_else' => true,
19 | 'no_unused_imports' => true,
20 | 'standardize_not_equals' => true,
21 | 'declare_strict_types' => true,
22 | 'is_null' => true,
23 | 'yoda_style' => false,
24 | 'no_empty_statement' => true,
25 | 'void_return' => true,
26 | 'list_syntax' => ['syntax' => 'short'],
27 | 'class_attributes_separation' => [
28 | 'elements' => [
29 | 'const',
30 | 'method',
31 | 'property',
32 | ]
33 | ],
34 | 'blank_line_before_statement' => [
35 | 'statements' => [
36 | 'return',
37 | ]
38 | ],
39 | ])
40 | ->setRiskyAllowed(true)
41 | ->setFinder($finder);
42 |
--------------------------------------------------------------------------------
/tests/Helper/DBALDriverResult.php:
--------------------------------------------------------------------------------
1 | source = $source;
23 | }
24 |
25 | public function fetchNumeric(): bool
26 | {
27 | return false;
28 | }
29 |
30 | public function fetchAssociative(): array
31 | {
32 | return $this->source['fetchAssociative'] ?? [];
33 | }
34 |
35 | public function fetchOne(): array
36 | {
37 | return [];
38 | }
39 |
40 | public function fetchAllNumeric(): array
41 | {
42 | return [];
43 | }
44 |
45 | public function fetchAllAssociative(): array
46 | {
47 | return [];
48 | }
49 |
50 | public function fetchFirstColumn(): array
51 | {
52 | return [];
53 | }
54 |
55 | public function rowCount(): int
56 | {
57 | return 0;
58 | }
59 |
60 | public function columnCount(): int
61 | {
62 | return 0;
63 | }
64 |
65 | public function free(): void
66 | {
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docs/guide/cookbook.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Cookbook
2 | =========================
3 |
4 | Tips, recommendations and best practices for use this library.
5 |
6 |
7 | ## :book: Guide
8 |
9 | * [Guide](./README.md)
10 | * [Install](./install.md)
11 | * [Transport](./transport.md)
12 | * [Configuration](./configuration.md)
13 | * [Producer (Send message)](./producer.md)
14 | * [Consuming](./consuming.md)
15 | * [Example](./example.md)
16 | * **[Cookbook](./cookbook.md)**
17 |
18 |
19 |
20 |
21 | ## :page_facing_up: Cookbook
22 |
23 | - If you are using docker, run consumer in an individual container. This will allow you to get away from blocking handling and speed up the application.
24 |
25 | - You can work with serialized objects, but it's better to avoid it.
26 | - First you load the queue with big data.
27 | - Secondly if you change the objects of your application, there is a risk of getting errors when processing messages (which were in the queue).
28 | - *However, this is a recommendation, not a hard and fast prescription, use common sense.*
29 |
30 | - If you are using jobs we recommend using job aliases instead of class namespace. Because in the case of a refactor class or namespace may change messages from the queue continue to be processed.
31 |
32 | - If you are using basic example from [consume.php](../../example/consume.php) you need to watch closely behind leaks in php process. [PHP is meant to die](https://software-gunslinger.tumblr.com/post/47131406821/php-is-meant-to-die).
--------------------------------------------------------------------------------
/tests/Serializer/SerializerTest.php:
--------------------------------------------------------------------------------
1 | serialize(['my_data']);
24 | $deserialize = $baseSerializer->deserialize($serialize);
25 |
26 | self::assertEquals(serialize(['my_data']), $serialize);
27 | self::assertEquals(['my_data'], $deserialize);
28 | }
29 |
30 | public function testSymfonySerializer(): void
31 | {
32 | $serializer = new Serializer(
33 | [
34 | ],
35 | [
36 | new JsonEncoder(),
37 | ]
38 | );
39 |
40 | $symfonySerializer = new SymfonySerializer();
41 |
42 | $serialize = $symfonySerializer->serialize(['my_data']);
43 | $deserialize = $symfonySerializer->deserialize($serialize);
44 |
45 | self::assertEquals($serializer->serialize(['my_data'], JsonEncoder::FORMAT), $serialize);
46 | self::assertEquals(['my_data'], $deserialize);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Priority.php:
--------------------------------------------------------------------------------
1 | priority = $value;
40 | }
41 |
42 | /**
43 | * @return string
44 | */
45 | public function __toString(): string
46 | {
47 | return (string)$this->priority;
48 | }
49 |
50 | /**
51 | * @return int
52 | */
53 | public function getValue(): int
54 | {
55 | return $this->priority;
56 | }
57 |
58 | /**
59 | * @return array
60 | */
61 | public static function getPriorities(): array
62 | {
63 | return [
64 | self::VERY_LOW,
65 | self::LOW,
66 | self::DEFAULT,
67 | self::HIGH,
68 | self::VERY_HIGH,
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/docs/guide/example.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Example
2 | ========================
3 |
4 | Examples and work demonstrating.
5 |
6 |
7 | ## :book: Guide
8 |
9 | * [Guide](./README.md)
10 | * [Install](./install.md)
11 | * [Transport](./transport.md)
12 | * [Configuration](./configuration.md)
13 | * [Producer (Send message)](./producer.md)
14 | * [Consuming](./consuming.md)
15 | * **[Example](./example.md)**
16 | * [Cookbook](./cookbook.md)
17 |
18 |
19 |
20 | ## :page_facing_up: Example
21 |
22 | Example of sending messages to a queue: [produce.php](../../example/produce.php)
23 |
24 | Example of processing messages from a queue: [consume.php](../../example/consume.php)
25 |
26 | Advanced example of processing messages from a queue: [advanced-consume.php](../../example/advanced-consume.php)
27 |
28 |
29 |
30 | ## Quick run with docker
31 |
32 | `cd .docker` - go to dir.
33 |
34 |
35 | **Run the following commands:**
36 |
37 | - `make build` - build docker container
38 | - `make start` - start docker container
39 | - `make php cmd='./example/consume.php'` - run consume (to read messages from the queue)
40 | - `make php cmd='./example/produce.php'` - run produce (to sent messages to the queue)
41 |
42 |
43 | > Both examples should work in different tabs because they are daemons (while(true){}).
44 |
45 |
46 |
47 | **PHP command access:**
48 |
49 | - `make php cmd='{you_command}'`:
50 | - Example: `make php cmd='-v'`:
51 |
52 |
53 |
54 | **Composer command access:**
55 | - `make composer cmd='{you_command}'`:
56 | - Example: - `make composer cmd='update'`:
--------------------------------------------------------------------------------
/tests/MessageStatusTest.php:
--------------------------------------------------------------------------------
1 | getValue());
22 | }
23 |
24 | public function testProcessStatus(): void
25 | {
26 | $status = new Status(Status::IN_PROCESS);
27 |
28 | self::assertEquals(Status::IN_PROCESS, $status->getValue());
29 | }
30 |
31 | public function testRedeliveredStatus(): void
32 | {
33 | $status = new Status(Status::REDELIVERED);
34 |
35 | self::assertEquals(Status::REDELIVERED, $status->getValue());
36 | }
37 |
38 | public function testErrorStatus(): void
39 | {
40 | $status = new Status(Status::FAILURE);
41 |
42 | self::assertEquals(Status::FAILURE, $status->getValue());
43 | }
44 |
45 | public function testAnotherStatus(): void
46 | {
47 | $this->expectException(InvalidArgumentException::class);
48 | $this->expectExceptionMessage(sprintf('"%s" is not a valid message status.', 'my_status'));
49 |
50 | new Status('my_status');
51 | }
52 |
53 | public function testStatusToString(): void
54 | {
55 | $status = new Status(Status::NEW);
56 |
57 | self::assertEquals(Status::NEW, (string)$status);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nepster-web/php-simple-queue",
3 | "description": "Simple queues implementation in PHP through database.",
4 | "keywords": [
5 | "php",
6 | "queue",
7 | "simple-queue",
8 | "dbal queue",
9 | "database queue",
10 | "consumer"
11 | ],
12 | "support": {
13 | "docs": "https://github.com/nepster-web/php-simple-queue/blob/main/docs/guide/README.md",
14 | "issues": "https://github.com/nepster-web/php-simple-queue/issues",
15 | "source": "https://github.com/nepster-web/php-simple-queue"
16 | },
17 | "config": {
18 | "sort-packages": true
19 | },
20 | "license": "MIT",
21 | "authors": [
22 | ],
23 | "require": {
24 | "php": "^7.4|^8.0",
25 | "ext-PDO": "*",
26 | "ext-json": "*",
27 | "doctrine/dbal": "^2.0|^3.0",
28 | "laminas/laminas-hydrator": "^4.1",
29 | "ramsey/uuid": "^4.1",
30 | "symfony/serializer": "^5.2"
31 | },
32 | "require-dev": {
33 | "roave/security-advisories": "dev-master",
34 | "phpunit/phpunit": "^9.5",
35 | "friendsofphp/php-cs-fixer": "^2.18"
36 | },
37 | "autoload": {
38 | "psr-4": {
39 | "Simple\\Queue\\": "src/"
40 | }
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "Simple\\QueueTest\\": "tests/"
45 | }
46 | },
47 | "scripts": {
48 | "code-style-check": "php vendor/bin/php-cs-fixer fix --verbose --show-progress=dots --dry-run --config=.php_cs.php",
49 | "code-style-fix": "php vendor/bin/php-cs-fixer fix --diff --config=.php_cs.php",
50 | "test": "php vendor/bin/phpunit --colors=always",
51 | "test-coverage": "php vendor/bin/phpunit --coverage-text --coverage-html ./docs/tmp/"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/docs/guide/transport.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Usage basics
2 | =============================
3 |
4 | Transport - provides methods for management messages in queue.
5 |
6 |
7 | ## :book: Guide
8 |
9 | * [Guide](./README.md)
10 | * [Install](./install.md)
11 | * **[Transport](./transport.md)**
12 | * [Configuration](./configuration.md)
13 | * [Producer (Send message)](./producer.md)
14 | * [Consuming](./consuming.md)
15 | * [Example](./example.md)
16 | * [Cookbook](./cookbook.md)
17 |
18 |
19 |
20 | > Currently only supported Doctrine DBAL.
21 |
22 |
23 |
24 | ## :page_facing_up: Transport
25 |
26 | The transport uses [Doctrine DBAL](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/) library and SQL
27 | like server as a broker. It creates a table there. Pushes and pops messages to\from that table.
28 |
29 |
30 |
31 | **Create connection:**
32 |
33 | You can get a DBAL Connection through the Doctrine\DBAL\DriverManager class.
34 |
35 | ```php
36 | $connection = \Doctrine\DBAL\DriverManager::getConnection([
37 | 'dbname' => 'my_db',
38 | 'user' => 'root',
39 | 'password' => '*******',
40 | 'host' => 'localhost',
41 | 'port' => '5432',
42 | 'driver' => 'pdo_pgsql',
43 | ]);
44 | ```
45 |
46 | or
47 |
48 | ```php
49 | $connection = \Doctrine\DBAL\DriverManager::getConnection([
50 | 'driver' => 'pdo_sqlite',
51 | 'path' => '/db/queue.db'
52 | ]);
53 | ```
54 |
55 | [See more information.](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)
56 |
57 |
58 |
59 | **Create transport:**
60 |
61 | ```php
62 | $transport = new \Simple\Queue\Transport\DoctrineDbalTransport($connection);
63 | ```
64 |
65 |
--------------------------------------------------------------------------------
/tests/MessagePriorityTest.php:
--------------------------------------------------------------------------------
1 | getValue());
22 | }
23 |
24 | public function testVeryLowPriority(): void
25 | {
26 | $priority = new Priority(Priority::VERY_LOW);
27 |
28 | self::assertEquals(Priority::VERY_LOW, $priority->getValue());
29 | }
30 |
31 | public function testLowPriority(): void
32 | {
33 | $priority = new Priority(Priority::LOW);
34 |
35 | self::assertEquals(Priority::LOW, $priority->getValue());
36 | }
37 |
38 | public function testHighPriority(): void
39 | {
40 | $priority = new Priority(Priority::HIGH);
41 |
42 | self::assertEquals(Priority::HIGH, $priority->getValue());
43 | }
44 |
45 | public function testVeryHighPriority(): void
46 | {
47 | $priority = new Priority(Priority::VERY_HIGH);
48 |
49 | self::assertEquals(Priority::VERY_HIGH, $priority->getValue());
50 | }
51 |
52 | public function testAnotherPriority(): void
53 | {
54 | $this->expectException(InvalidArgumentException::class);
55 | $this->expectExceptionMessage(sprintf('"%s" is not a valid message priority.', 777));
56 |
57 | new Priority(777);
58 | }
59 |
60 | public function testPriorityToString(): void
61 | {
62 | $priority = new Priority(Priority::VERY_LOW);
63 |
64 | self::assertEquals(Priority::VERY_LOW, (int)(string)$priority);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Status.php:
--------------------------------------------------------------------------------
1 | status = $value;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | public function __toString(): string
61 | {
62 | return $this->status;
63 | }
64 |
65 | /**
66 | * @return string
67 | */
68 | public function getValue(): string
69 | {
70 | return $this->status;
71 | }
72 |
73 | /**
74 | * @return array
75 | */
76 | public static function getStatuses(): array
77 | {
78 | return [
79 | self::NEW,
80 | self::IN_PROCESS,
81 | self::FAILURE,
82 | self::REDELIVERED,
83 | self::UNDEFINED_HANDLER,
84 | ];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Change Log
2 | ===========================
3 |
4 | A changelog of all notable changes made to this library.
5 |
6 | - *ENH*: Enhance or modify
7 | - *FIX*: Bug fix or a small change
8 |
9 |
10 |
11 | 1.0.0 under development
12 | ----------------------
13 |
14 |
15 | 1.0.0-RC under development
16 | ----------------------
17 |
18 |
19 | 1.0.0-Beta under development
20 | ----------------------
21 |
22 |
23 | 1.0.0-Alpha-5 May 9, 2021
24 | ---------------------------
25 | - *FIX*: [#28](https://github.com/nepster-web/php-simple-queue/issues/28) - set format for datetime
26 |
27 |
28 | 1.0.0-Alpha-4 April 7, 2021
29 | ---------------------------
30 | - *ENH*: [#22](https://github.com/nepster-web/php-simple-queue/issues/22) - implementation [Context](./src/Context.php) for jobs and processors
31 | - *ENH*: Improved documentation
32 |
33 |
34 | 1.0.0-Alpha-3 March 31, 2021
35 | ---------------------------
36 | - *ENH*: [composer.json](./composer.json) updating package versions
37 | - *ENH*: Implemented abstraction for ([Transport](./src/Transport/DoctrineDbalTransport.php))
38 | - *ENH*: Config expanded (job registration and processor registration)
39 | - *ENH*: Refactoring class architecture (tests updating)
40 | - *ENH*: Improved documentation
41 | - *FIX*: Fixed data loss when redelivery message
42 |
43 |
44 | 1.0.0-Alpha-2 March 17, 2021
45 | ----------------------------
46 | - *ENH*: added work with jobs
47 | - *ENH*: added work with processors
48 | - *ENH*: added serializer fo message body
49 | - *ENH*: added [MessageHydrator](./src/MessageHydrator.php) (for change system properties)
50 | - *ENH*: added base [Config](./src/Config.php)
51 | - *ENH*: expanded consumer work algorithms
52 | - *ENH*: increased test coverage
53 | - *ENH*: improved documentation
54 | - *ENH*: updated Dockerfile in example (strict version for: php, composer, xdebug)
55 |
56 |
57 | 1.0.0-Alpha March 13, 2021
58 | --------------------------
59 | - *ENH*: Repository configuration (travis, scrutinizer, php cs, etc)
60 | - *ENH*: Add simple example with consume and produce
61 | - *ENH*: Ability to run example with docker
62 |
63 |
64 | Release February 15, 2021
65 | -------------------------
66 | - Create guide
67 | - Create example
68 | - Create tests
69 | - Initial release
--------------------------------------------------------------------------------
/src/Transport/DoctrineDbalTableCreator.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
30 | }
31 |
32 | /**
33 | * @param string $tableName
34 | */
35 | public static function changeTableName(string $tableName): void
36 | {
37 | self::$tableName = $tableName;
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public static function getTableName(): string
44 | {
45 | return self::$tableName;
46 | }
47 |
48 | /**
49 | * Creating a queue table
50 | *
51 | * @throws \Doctrine\DBAL\Exception
52 | * @throws \Doctrine\DBAL\Schema\SchemaException
53 | */
54 | public function createDataBaseTable(): void
55 | {
56 | $schemaManager = $this->connection->getSchemaManager();
57 |
58 | $tableExists = $schemaManager ? $schemaManager->tablesExist([self::getTableName()]) : false;
59 |
60 | if ($schemaManager === null || $tableExists === true) {
61 | return;
62 | }
63 |
64 | $table = new Table(self::getTableName());
65 |
66 | $table->addColumn('id', Types::GUID, ['length' => 16, 'fixed' => true]);
67 | $table->addColumn('status', Types::STRING);
68 | $table->addColumn('attempts', Types::SMALLINT);
69 | $table->addColumn('queue', Types::STRING);
70 | $table->addColumn('event', Types::STRING, ['notnull' => false]);
71 | $table->addColumn('is_job', Types::BOOLEAN, ['default' => false]);
72 | $table->addColumn('body', Types::TEXT, ['notnull' => false]);
73 | $table->addColumn('priority', Types::SMALLINT, ['notnull' => false]);
74 | $table->addColumn('error', Types::TEXT, ['notnull' => false]);
75 | $table->addColumn('redelivered_at', Types::DATETIME_IMMUTABLE, ['notnull' => false]);
76 | $table->addColumn('created_at', Types::DATETIME_IMMUTABLE);
77 | $table->addColumn('exact_time', Types::BIGINT);
78 |
79 | $table->setPrimaryKey(['id']);
80 | $table->addIndex(['priority', 'created_at', 'queue', 'status', 'event', 'id']);
81 |
82 | $schemaManager->createTable($table);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tests/Transport/QueueTableCreatorTest.php:
--------------------------------------------------------------------------------
1 | createDataBaseTable();
42 |
43 | /** @var Table $table */
44 | $table = $schemaManager::$data['createTable'];
45 |
46 | $tableColumns = [];
47 |
48 | /** @var Column $column */
49 | foreach ($table->getColumns() as $name => $column) {
50 | $tableColumns[$name] = $column->getType()->getName();
51 | }
52 |
53 | $expected = [
54 | 'id' => 'guid',
55 | 'status' => 'string',
56 | 'attempts' => 'smallint',
57 | 'queue' => 'string',
58 | 'event' => 'string',
59 | 'is_job' => 'boolean',
60 | 'body' => 'text',
61 | 'priority' => 'smallint',
62 | 'error' => 'text',
63 | 'redelivered_at' => 'datetime_immutable',
64 | 'created_at' => 'datetime_immutable',
65 | 'exact_time' => 'bigint',
66 | ];
67 |
68 | self::assertEquals($expected, $tableColumns);
69 | }
70 |
71 | public function testSimulateTableCreationWithoutTableCrate(): void
72 | {
73 | $data = [];
74 |
75 | $schemaManager = new class extends MockSchemaManager {
76 | public function tablesExist($names): bool
77 | {
78 | self::$data['tablesExist'] = true;
79 |
80 | return true;
81 | }
82 | };
83 | $connection = new MockConnection($schemaManager);
84 |
85 | $queueTableCreator = new DoctrineDbalTableCreator($connection);
86 |
87 | $queueTableCreator->createDataBaseTable();
88 |
89 | $tablesExist = $schemaManager::$data['tablesExist'];
90 |
91 | self::assertTrue($tablesExist);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/docs/guide/producer.md:
--------------------------------------------------------------------------------
1 | PHP Simple Queue Usage basics
2 | =============================
3 |
4 | Message producer object to send messages to a queue.
5 |
6 |
7 | ## :book: Guide
8 |
9 | * [Guide](./README.md)
10 | * [Install](./install.md)
11 | * [Transport](./transport.md)
12 | * [Configuration](./configuration.md)
13 | * **[Producer (Send message)](./producer.md)**
14 | * [Consuming](./consuming.md)
15 | * [Example](./example.md)
16 | * [Cookbook](./cookbook.md)
17 |
18 |
19 |
20 | ## :page_facing_up: Producer
21 |
22 | You need to configure [$transport](./transport.md) and [$config](./configuration.md) to send new messages.
23 |
24 |
25 |
26 | **Send a new message to queue:**
27 | -------------------------------
28 |
29 | ```php
30 | $producer = new \Simple\Queue\Producer($transport, $config);
31 |
32 | $producer->send($producer->createMessage('my_queue', ['my_data']));
33 | ```
34 |
35 | or a custom example (you need to think about serialization):
36 |
37 | ```php
38 | $producer = new \Simple\Queue\Producer($transport, $config);
39 |
40 | $message = (new \Simple\Queue\Message('my_queue', 'my_data'))
41 | ->withEvent('my_event')
42 | ->changePriority(\Simple\Queue\Priority::VERY_HIGH);
43 |
44 | $producer->send($message);
45 | ```
46 |
47 | You can send a message from anywhere in the application to process it in the background.
48 |
49 |
50 |
51 | **Send a new message to queue through job:**
52 | -------------------------------
53 |
54 | ```php
55 | $producer = new \Simple\Queue\Producer($transport, $config);
56 |
57 | $producer->dispatch(MyJob::class, ['key' => 'value']);
58 | ```
59 |
60 |
61 |
62 | > You can send a message to the queue from anywhere in the application where available $producer.
63 |
64 |
65 |
66 | **Message**
67 | ----------------------
68 |
69 | Description of the base entity [Message](../../src/Message.php).
70 |
71 | ```php
72 |
73 | // create new Message
74 | $message = new \Simple\Queue\Message('my_queue', 'my_data');
75 |
76 | // public getters
77 | $message->getId();
78 | $message->getStatus();
79 | $message->isJob();
80 | $message->getError();
81 | $message->getExactTime();
82 | $message->getCreatedAt();
83 | $message->getAttempts();
84 | $message->getQueue();
85 | $message->getEvent();
86 | $message->getBody();
87 | $message->getPriority();
88 | $message->getRedeliveredAt();
89 | $message->isRedelivered();
90 |
91 | // public setters
92 | $message->changeRedeliveredAt($redeliveredAt);
93 | $message->changeQueue($queue);
94 | $message->changePriority($priority);
95 | $message->withEvent($event);
96 | ```
97 |
98 | Each message has [Status](../../src/Status.php) and [Priority](../../src/Priority.php).
99 |
100 | * **Status**
101 | Used to delimit messages in a queue (system parameter, not available for public modification).
102 | Possible options: NEW; IN_PROCESS; ERROR; REDELIVERED.
103 |
104 |
105 | * **Priority**
106 | Used to sort messages in the consumer.
107 | Possible options: VERY_LOW = -2; LOW = -1; DEFAULT = 0; HIGH = 1; VERY_HIGH = 2.
--------------------------------------------------------------------------------
/tests/Helper/MockConnection.php:
--------------------------------------------------------------------------------
1 | abstractSchemaManager = $abstractSchemaManager ?: new MockSchemaManager();
38 | $this->source = $source;
39 |
40 | $driver = new class extends AbstractSQLiteDriver {
41 | public function connect(array $params): void
42 | {
43 | }
44 | };
45 |
46 | parent::__construct([], $driver);
47 | }
48 |
49 | /**
50 | * @inheritDoc
51 | */
52 | public function insert($table, array $data, array $types = []): int
53 | {
54 | self::$data['insert'] = [
55 | 'table' => $table,
56 | 'data' => $data,
57 | 'types' => $types,
58 | ];
59 |
60 | return $this->source['insert'] ?? 0;
61 | }
62 |
63 | /**
64 | * @inheritDoc
65 | */
66 | public function update($table, array $data, array $criteria, array $types = []): int
67 | {
68 | self::$data['update'] = [
69 | 'table' => $table,
70 | 'data' => $data,
71 | 'criteria' => $criteria,
72 | 'types' => $types,
73 | ];
74 |
75 | return 0;
76 | }
77 |
78 | /**
79 | * @inheritDoc
80 | */
81 | public function delete($table, array $criteria, array $types = []): int
82 | {
83 | self::$data['delete'] = [
84 | 'table' => $table,
85 | 'criteria' => $criteria,
86 | 'types' => $types,
87 | ];
88 |
89 | return 0;
90 | }
91 |
92 | /**
93 | * @return AbstractSchemaManager|MockSchemaManager
94 | */
95 | public function getSchemaManager(): AbstractSchemaManager
96 | {
97 | return $this->abstractSchemaManager;
98 | }
99 |
100 | /**
101 | * @inheritDoc
102 | */
103 | public function executeQuery(string $sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): Result
104 | {
105 | $result = new DBALDriverResult($this->source);
106 |
107 | return new Result($result, $this);
108 | }
109 |
110 | /**
111 | * @return QueryBuilder
112 | */
113 | public function createQueryBuilder(): QueryBuilder
114 | {
115 | return new class($this) extends QueryBuilder {
116 | };
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/ConsumerTest.php:
--------------------------------------------------------------------------------
1 | getId();
37 | }
38 | };
39 |
40 | $consumer = new Consumer($transport, new Producer($transport));
41 | $consumer->acknowledge($message);
42 |
43 | self::assertEquals($id, $transport::$deleteMessageId);
44 | }
45 |
46 | public function testRejectWithRequeue(): void
47 | {
48 | $id = '71a384ad-952d-417f-9dc5-dfdb5b01704d';
49 | $message = new Message('my_queue', 'my_data');
50 |
51 | MessageHydrator::changeProperty($message, 'id', $id);
52 |
53 | $transport = new class(new MockConnection()) extends DoctrineDbalTransport {
54 | public static string $deleteMessageId;
55 |
56 | public function deleteMessage(Message $message): void
57 | {
58 | self::$deleteMessageId = $message->getId();
59 | }
60 |
61 | public static Message $message;
62 |
63 | public function send(Message $message): void
64 | {
65 | self::$message = $message;
66 | }
67 | };
68 |
69 | $producer = new Producer($transport);
70 |
71 | $consumer = new Consumer($transport, $producer);
72 | $consumer->reject($message, true);
73 |
74 | self::assertEquals($id, $transport::$deleteMessageId);
75 |
76 | self::assertEquals(Status::REDELIVERED, $transport::$message->getStatus());
77 | self::assertEquals(
78 | (new DateTimeImmutable())
79 | ->modify(sprintf('+%s seconds', Config::getDefault()->getRedeliveryTimeInSeconds()))
80 | ->format('Y-m-d H:i:s'),
81 | $transport::$message->getRedeliveredAt()->format('Y-m-d H:i:s')
82 | );
83 | }
84 |
85 | public function testRejectWithoutRequeue(): void
86 | {
87 | $id = '71a384ad-952d-417f-9dc5-dfdb5b01704d';
88 | $message = new Message('my_queue', 'my_data');
89 |
90 | MessageHydrator::changeProperty($message, 'id', $id);
91 |
92 | $transport = new class(new MockConnection()) extends DoctrineDbalTransport {
93 | public static string $deleteMessageId;
94 |
95 | public function deleteMessage(Message $message): void
96 | {
97 | self::$deleteMessageId = $message->getId();
98 | }
99 | };
100 |
101 | $consumer = new Consumer($transport, new Producer($transport));
102 | $consumer->reject($message);
103 |
104 | self::assertEquals($id, $transport::$deleteMessageId);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/MessageTest.php:
--------------------------------------------------------------------------------
1 | expectException(QueueException::class);
30 | $this->expectExceptionMessage('The message has no id. It looks like it was not sent to the queue.');
31 | $message->getId();
32 |
33 | self::assertNull($message->getEvent());
34 | self::assertNull($message->getError());
35 | self::assertNull($message->getRedeliveredAt());
36 |
37 | self::assertEquals(0, $message->getAttempts());
38 | self::assertEquals('my_queue', $message->getQueue());
39 | self::assertEquals($body, $message->getBody());
40 | self::assertEquals(Status::NEW, $message->getStatus());
41 | self::assertEquals(Priority::DEFAULT, $message->getPriority());
42 | self::assertEquals($time, $message->getExactTime());
43 | self::assertEquals(date('Y-m-d H:i:s', $time), $message->getCreatedAt()->format('Y-m-d H:i:s'));
44 | }
45 |
46 | public function testCreateNewDefaultMessageWithCeil(): void
47 | {
48 | $body = json_encode([], JSON_THROW_ON_ERROR);
49 |
50 | $time = time();
51 | $message = (new Message('my_queue', $body))
52 | ->changePriority(Priority::LOW)
53 | ->withEvent('my_event')
54 | ->changeRedeliveredAt(new DateTimeImmutable());
55 |
56 | self::assertEquals(0, $message->getAttempts());
57 | self::assertEquals('my_queue', $message->getQueue());
58 | self::assertEquals('my_event', $message->getEvent());
59 | self::assertEquals($body, $message->getBody());
60 | self::assertEquals(Priority::LOW, $message->getPriority());
61 |
62 | self::assertEquals(date('Y-m-d H:i:s', $time), $message->getCreatedAt()->format('Y-m-d H:i:s'));
63 | }
64 |
65 | public function testChangePriority(): void
66 | {
67 | $message = new Message('my_queue', '');
68 | $message->changePriority(Priority::HIGH);
69 |
70 | self::assertEquals(Priority::HIGH, $message->getPriority());
71 | }
72 |
73 | public function testChangeQueue(): void
74 | {
75 | $message = new Message('my_queue', '');
76 | $message->changeQueue('new_queue');
77 |
78 | self::assertEquals('new_queue', $message->getQueue());
79 | }
80 |
81 | public function testSetEvent(): void
82 | {
83 | $message = new Message('my_queue', '');
84 | $message->withEvent('my_event');
85 |
86 | self::assertEquals('my_event', $message->getEvent());
87 | }
88 |
89 | public function testRedeliveredAt(): void
90 | {
91 | $redelivered = new DateTimeImmutable();
92 |
93 | $message = new Message('my_queue', '');
94 | $message->changeRedeliveredAt($redelivered);
95 |
96 | self::assertTrue($message->isRedelivered());
97 | self::assertEquals($redelivered->format('Y-m-d H:i:s'), $message->getRedeliveredAt()->format('Y-m-d H:i:s'));
98 | }
99 |
100 | public function testRedeliveredAtByStatus(): void
101 | {
102 | $message = new Message('my_queue', '');
103 |
104 | $message = (new MessageHydrator($message))
105 | ->changeStatus(Status::REDELIVERED)
106 | ->getMessage();
107 |
108 | self::assertTrue($message->isRedelivered());
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Producer.php:
--------------------------------------------------------------------------------
1 | transport = $transport;
33 | $this->config = $config ?: Config::getDefault();
34 | }
35 |
36 | /**
37 | * Create new message
38 | *
39 | * @param string $queue
40 | * @param $body
41 | * @return Message
42 | * @throws QueueException
43 | */
44 | public function createMessage(string $queue, $body): Message
45 | {
46 | if (is_callable($body)) {
47 | throw new QueueException('The closure cannot be serialized.');
48 | }
49 |
50 | if (is_object($body) && method_exists($body, '__toString')) {
51 | $body = (string)$body;
52 | }
53 |
54 | if (is_object($body) || is_array($body)) {
55 | $body = $this->config->getSerializer()->serialize($body);
56 | }
57 |
58 | return new Message($queue, (string)$body);
59 | }
60 |
61 | /**
62 | * Redelivered a message to the queue
63 | *
64 | * @param Message $message
65 | * @return Message
66 | */
67 | public function makeRedeliveryMessage(Message $message): Message
68 | {
69 | $newStatus = ($message->getStatus() === Status::NEW || $message->getStatus() === Status::IN_PROCESS) ?
70 | Status::REDELIVERED :
71 | $message->getStatus();
72 |
73 | $redeliveredTime = (new DateTimeImmutable('now'))
74 | ->modify(sprintf('+%s seconds', $this->config->getRedeliveryTimeInSeconds()));
75 |
76 | if (
77 | $message->getRedeliveredAt() &&
78 | $message->getRedeliveredAt()->getTimestamp() > $redeliveredTime->getTimestamp()
79 | ) {
80 | $redeliveredTime = $message->getRedeliveredAt();
81 | }
82 |
83 | if ($newStatus === Status::FAILURE) {
84 | $redeliveredTime = null;
85 | }
86 |
87 | $redeliveredMessage = (new Message($message->getQueue(), $message->getBody()))
88 | ->changePriority($message->getPriority())
89 | ->withEvent($message->getEvent())
90 | ->changeRedeliveredAt($redeliveredTime);
91 |
92 | return (new MessageHydrator($redeliveredMessage))
93 | ->changeStatus($newStatus)
94 | ->jobable($message->isJob())
95 | ->setError($message->getError())
96 | ->changeAttempts($message->getAttempts() + 1)
97 | ->getMessage();
98 | }
99 |
100 | /**
101 | * Dispatch a job
102 | *
103 | * @param string $jobName
104 | * @param array $data
105 | * @throws QueueException
106 | */
107 | public function dispatch(string $jobName, array $data): void
108 | {
109 | $job = $this->config->getJob($jobName);
110 |
111 | $message = $this->createMessage($job->queue(), $data)
112 | ->withEvent($this->config->getJobAlias($jobName));
113 |
114 | $message = (new MessageHydrator($message))->jobable()->getMessage();
115 |
116 | $this->send($message);
117 | }
118 |
119 | /**
120 | * Send message to queue
121 | *
122 | * @param Message $message
123 | */
124 | public function send(Message $message): void
125 | {
126 | $this->transport->send($message);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/MessageHydrator.php:
--------------------------------------------------------------------------------
1 | message = clone $message;
33 | }
34 |
35 | /**
36 | * @param string $status
37 | * @return MessageHydrator
38 | */
39 | public function changeStatus(string $status): self
40 | {
41 | $this->message = $this->hydrate(['status' => new Status($status)]);
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * @param bool $isJob
48 | * @return $this
49 | */
50 | public function jobable(bool $isJob = true): self
51 | {
52 | $this->message = $this->hydrate(['isJob' => $isJob]);
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * @param string|null $error
59 | * @return $this
60 | */
61 | public function setError(?string $error): self
62 | {
63 | $this->message = $this->hydrate(['error' => $error]);
64 |
65 | return $this;
66 | }
67 |
68 | /**
69 | * @return $this
70 | */
71 | public function increaseAttempt(): self
72 | {
73 | $this->hydrate(['attempts' => $this->message->getAttempts() + 1]);
74 |
75 | return $this;
76 | }
77 |
78 | /**
79 | * @param int $amount
80 | * @return $this
81 | */
82 | public function changeAttempts(int $amount): self
83 | {
84 | $this->hydrate(['attempts' => $amount]);
85 |
86 | return $this;
87 | }
88 |
89 | /**
90 | * @return Message
91 | */
92 | public function getMessage(): Message
93 | {
94 | return $this->message;
95 | }
96 |
97 | /**
98 | * @param Message $message
99 | * @param string $property
100 | * @param $value
101 | * @throws ReflectionException
102 | */
103 | public static function changeProperty(Message $message, string $property, $value): void
104 | {
105 | $r = new ReflectionProperty($message, $property);
106 | $r->setAccessible(true);
107 | $r->setValue($message, $value);
108 | }
109 |
110 | /**
111 | * Create entity Message from array data
112 | *
113 | * @param array $data
114 | * @return Message
115 | * @throws Exception
116 | */
117 | public static function createMessage(array $data): Message
118 | {
119 | $strategy = new HydratorStrategy(new ReflectionHydrator(), Message::class);
120 |
121 | /** @var Message $message */
122 | $message = $strategy->hydrate(array_merge($data, [
123 | 'queue' => $data['queue'] ?? 'default',
124 | 'event' => $data['event'] ?? null,
125 | 'isJob' => $data['is_job'] ?? false,
126 | 'body' => $data['body'] ?? '',
127 | 'error' => $data['error'] ?? null,
128 | 'attempts' => $data['attempts'] ?? 0,
129 | 'status' => new Status($data['status'] ?? Status::NEW),
130 | 'priority' => new Priority((int)($data['priority'] ?? Priority::DEFAULT)),
131 | 'exactTime' => $data['exact_time'] ?? time(),
132 | 'createdAt' => new DateTimeImmutable($data['created_at'] ?? 'now'),
133 | 'redeliveredAt' => isset($data['redelivered_at']) ? new DateTimeImmutable($data['redelivered_at']) : null,
134 | ]));
135 |
136 | return $message;
137 | }
138 |
139 | /**
140 | * @param array $data
141 | * @return Message
142 | */
143 | protected function hydrate(array $data): Message
144 | {
145 | /** @var Message $redeliveredMessage */
146 | $redeliveredMessage = (new ReflectionHydrator())->hydrate($data, $this->message);
147 |
148 | return $redeliveredMessage;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > This package is under development. Api classes of this application can be changed.
2 |
3 |
4 |
16 |
17 |