From 00ad4a6bfba7f7ae61a313d2aeec0adb8ec8b781 Mon Sep 17 00:00:00 2001 From: viktorprogger Date: Sun, 15 Sep 2024 22:25:53 +0500 Subject: [PATCH 1/7] Allow to restore original pushed message class on consume --- src/Message/EnvelopeTrait.php | 12 +++++++ src/Message/JsonMessageSerializer.php | 35 ++++++++++++------ src/Message/Message.php | 5 +++ src/Message/MessageInterface.php | 2 ++ .../Message/JsonMessageSerializerTest.php | 36 +++++++++++++++---- tests/Unit/Support/TestMessage.php | 30 ++++++++++++++++ 6 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 tests/Unit/Support/TestMessage.php diff --git a/src/Message/EnvelopeTrait.php b/src/Message/EnvelopeTrait.php index cc52cf15..bfe257a2 100644 --- a/src/Message/EnvelopeTrait.php +++ b/src/Message/EnvelopeTrait.php @@ -8,6 +8,18 @@ trait EnvelopeTrait { private MessageInterface $message; + /** + * A mirror of {@see MessageInterface::fromData()} + */ + abstract public static function fromMessage(MessageInterface $message): self; + + public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface + { + $message = new Message($handlerName, $data, $metadata); + + return self::fromMessage($message); + } + public function getMessage(): MessageInterface { return $this->message; diff --git a/src/Message/JsonMessageSerializer.php b/src/Message/JsonMessageSerializer.php index 81a6220c..a2b0c718 100644 --- a/src/Message/JsonMessageSerializer.php +++ b/src/Message/JsonMessageSerializer.php @@ -18,6 +18,7 @@ public function serialize(MessageInterface $message): string 'name' => $message->getHandlerName(), 'data' => $message->getData(), 'meta' => $message->getMetadata(), + 'class' => $message instanceof EnvelopeInterface ? get_class($message->getMessage()) : get_class($message), ]; return json_encode($payload, JSON_THROW_ON_ERROR); @@ -34,25 +35,37 @@ public function unserialize(string $value): MessageInterface throw new InvalidArgumentException('Payload must be array. Got ' . get_debug_type($payload) . '.'); } + $name = $payload['name'] ?? null; + if (!isset($name) || !is_string($name)) { + throw new InvalidArgumentException('Handler name must be string. Got ' . get_debug_type($name) . '.'); + } + $meta = $payload['meta'] ?? []; if (!is_array($meta)) { throw new InvalidArgumentException('Metadata must be array. Got ' . get_debug_type($meta) . '.'); } - // TODO: will be removed later - $message = new Message($payload['name'] ?? '$name', $payload['data'] ?? null, $meta); - + $envelopes = []; if (isset($meta[EnvelopeInterface::ENVELOPE_STACK_KEY]) && is_array($meta[EnvelopeInterface::ENVELOPE_STACK_KEY])) { - $message = $message->withMetadata( - array_merge($message->getMetadata(), [EnvelopeInterface::ENVELOPE_STACK_KEY => []]), - ); - foreach ($meta[EnvelopeInterface::ENVELOPE_STACK_KEY] as $envelope) { - if (is_string($envelope) && class_exists($envelope) && is_subclass_of($envelope, EnvelopeInterface::class)) { - $message = $envelope::fromMessage($message); - } - } + $envelopes = $meta[EnvelopeInterface::ENVELOPE_STACK_KEY]; } + $meta[EnvelopeInterface::ENVELOPE_STACK_KEY] = []; + $class = $payload['class'] ?? Message::class; + if (!is_subclass_of($class, MessageInterface::class)) { + $class = Message::class; + } + + /** + * @var class-string $class + */ + $message = $class::fromData($name, $payload['data'] ?? null, $meta); + + foreach ($envelopes as $envelope) { + if (is_string($envelope) && class_exists($envelope) && is_subclass_of($envelope, EnvelopeInterface::class)) { + $message = $envelope::fromMessage($message); + } + } return $message; } diff --git a/src/Message/Message.php b/src/Message/Message.php index a414ffb0..ab85d069 100644 --- a/src/Message/Message.php +++ b/src/Message/Message.php @@ -18,6 +18,11 @@ public function __construct( ) { } + public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface + { + return new self($handlerName, $data, $metadata); + } + public function getHandlerName(): string { return $this->handlerName; diff --git a/src/Message/MessageInterface.php b/src/Message/MessageInterface.php index c65f32fd..ea3b1882 100644 --- a/src/Message/MessageInterface.php +++ b/src/Message/MessageInterface.php @@ -6,6 +6,8 @@ interface MessageInterface { + public static function fromData(string $handlerName, mixed $data, array $metadata = []): self; + /** * Returns handler name. * diff --git a/tests/Unit/Message/JsonMessageSerializerTest.php b/tests/Unit/Message/JsonMessageSerializerTest.php index 776a9835..27efc892 100644 --- a/tests/Unit/Message/JsonMessageSerializerTest.php +++ b/tests/Unit/Message/JsonMessageSerializerTest.php @@ -11,6 +11,7 @@ use Yiisoft\Queue\Message\JsonMessageSerializer; use Yiisoft\Queue\Message\Message; use Yiisoft\Queue\Message\MessageInterface; +use Yiisoft\Queue\Tests\Unit\Support\TestMessage; /** * Testing message serialization options @@ -42,7 +43,7 @@ public static function dataUnsupportedPayloadFormat(): iterable */ public function testMetadataFormat(mixed $meta): void { - $payload = ['data' => 'test', 'meta' => $meta]; + $payload = ['name' => 'handler', 'data' => 'test', 'meta' => $meta]; $serializer = $this->createSerializer(); $this->expectExceptionMessage(sprintf('Metadata must be array. Got %s.', get_debug_type($meta))); @@ -59,31 +60,32 @@ public static function dataUnsupportedMetadataFormat(): iterable public function testUnserializeFromData(): void { - $payload = ['data' => 'test']; + $payload = ['name' => 'handler', 'data' => 'test']; $serializer = $this->createSerializer(); $message = $serializer->unserialize(json_encode($payload)); $this->assertInstanceOf(MessageInterface::class, $message); $this->assertEquals($payload['data'], $message->getData()); - $this->assertEquals([], $message->getMetadata()); + $this->assertEquals([EnvelopeInterface::ENVELOPE_STACK_KEY => []], $message->getMetadata()); } public function testUnserializeWithMetadata(): void { - $payload = ['data' => 'test', 'meta' => ['int' => 1, 'str' => 'string', 'bool' => true]]; + $payload = ['name' => 'handler', 'data' => 'test', 'meta' => ['int' => 1, 'str' => 'string', 'bool' => true]]; $serializer = $this->createSerializer(); $message = $serializer->unserialize(json_encode($payload)); $this->assertInstanceOf(MessageInterface::class, $message); $this->assertEquals($payload['data'], $message->getData()); - $this->assertEquals(['int' => 1, 'str' => 'string', 'bool' => true], $message->getMetadata()); + $this->assertEquals(['int' => 1, 'str' => 'string', 'bool' => true, EnvelopeInterface::ENVELOPE_STACK_KEY => []], $message->getMetadata()); } public function testUnserializeEnvelopeStack(): void { $payload = [ + 'name' => 'handler', 'data' => 'test', 'meta' => [ EnvelopeInterface::ENVELOPE_STACK_KEY => [ @@ -113,7 +115,7 @@ public function testSerialize(): void $json = $serializer->serialize($message); $this->assertEquals( - '{"name":"handler","data":"test","meta":[]}', + '{"name":"handler","data":"test","meta":[],"class":"Yiisoft\\\\Queue\\\\Message\\\\Message"}', $json, ); } @@ -129,9 +131,10 @@ public function testSerializeEnvelopeStack(): void $this->assertEquals( sprintf( - '{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id"}}', + '{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id"},"class":"%s"}', str_replace('\\', '\\\\', IdEnvelope::class), IdEnvelope::MESSAGE_ID_KEY, + str_replace('\\', '\\\\', Message::class), ), $json, ); @@ -153,6 +156,25 @@ public function testSerializeEnvelopeStack(): void ], $message->getMessage()->getMetadata()); } + public function testRestoreOriginalMessageClass(): void + { + $message = new TestMessage(); + $serializer = $this->createSerializer(); + $serializer->unserialize($serializer->serialize($message)); + + $this->assertInstanceOf(TestMessage::class, $message); + } + + public function testRestoreOriginalMessageClassWithEnvelope(): void + { + $message = new IdEnvelope(new TestMessage()); + $serializer = $this->createSerializer(); + $serializer->unserialize($serializer->serialize($message)); + + $this->assertInstanceOf(IdEnvelope::class, $message); + $this->assertInstanceOf(TestMessage::class, $message->getMessage()); + } + private function createSerializer(): JsonMessageSerializer { return new JsonMessageSerializer(); diff --git a/tests/Unit/Support/TestMessage.php b/tests/Unit/Support/TestMessage.php new file mode 100644 index 00000000..a4c0ab8d --- /dev/null +++ b/tests/Unit/Support/TestMessage.php @@ -0,0 +1,30 @@ + Date: Sun, 15 Sep 2024 17:26:09 +0000 Subject: [PATCH 2/7] Apply fixes from StyleCI --- .../Implementation/ExponentialDelayMiddlewareTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php b/tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php index 707a1ad7..29ac6827 100644 --- a/tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php +++ b/tests/Unit/Middleware/FailureHandling/Implementation/ExponentialDelayMiddlewareTest.php @@ -164,7 +164,8 @@ public function testPipelineFailure(): void $message = new Message( 'test', null, - [FailureEnvelope::FAILURE_META_KEY => [ExponentialDelayMiddleware::META_KEY_ATTEMPTS . '-test' => 2]]); + [FailureEnvelope::FAILURE_META_KEY => [ExponentialDelayMiddleware::META_KEY_ATTEMPTS . '-test' => 2]] + ); $queue = $this->createMock(QueueInterface::class); $middleware = new ExponentialDelayMiddleware( 'test', From 6c7840646d50c70e68d1738f6ed7aadf67a85cc1 Mon Sep 17 00:00:00 2001 From: viktorprogger Date: Sun, 15 Sep 2024 17:28:36 +0000 Subject: [PATCH 3/7] Apply Rector changes (CI) --- src/Message/JsonMessageSerializer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Message/JsonMessageSerializer.php b/src/Message/JsonMessageSerializer.php index a2b0c718..8f30f1fb 100644 --- a/src/Message/JsonMessageSerializer.php +++ b/src/Message/JsonMessageSerializer.php @@ -18,7 +18,7 @@ public function serialize(MessageInterface $message): string 'name' => $message->getHandlerName(), 'data' => $message->getData(), 'meta' => $message->getMetadata(), - 'class' => $message instanceof EnvelopeInterface ? get_class($message->getMessage()) : get_class($message), + 'class' => $message instanceof EnvelopeInterface ? $message->getMessage()::class : $message::class, ]; return json_encode($payload, JSON_THROW_ON_ERROR); From 9eb4dd92881fd278f40056ff6ecb9882111d06f2 Mon Sep 17 00:00:00 2001 From: viktorprogger Date: Wed, 18 Sep 2024 19:11:36 +0500 Subject: [PATCH 4/7] Move message class name to metadata and fix tests --- src/Message/JsonMessageSerializer.php | 12 ++++++++---- tests/Unit/Message/JsonMessageSerializerTest.php | 8 +++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Message/JsonMessageSerializer.php b/src/Message/JsonMessageSerializer.php index 8f30f1fb..277b182a 100644 --- a/src/Message/JsonMessageSerializer.php +++ b/src/Message/JsonMessageSerializer.php @@ -18,8 +18,12 @@ public function serialize(MessageInterface $message): string 'name' => $message->getHandlerName(), 'data' => $message->getData(), 'meta' => $message->getMetadata(), - 'class' => $message instanceof EnvelopeInterface ? $message->getMessage()::class : $message::class, ]; + if (!isset($payload['meta']['message-class'])) { + $payload['meta']['message-class'] = $message instanceof EnvelopeInterface + ? $message->getMessage()::class + : $message::class; + } return json_encode($payload, JSON_THROW_ON_ERROR); } @@ -37,12 +41,12 @@ public function unserialize(string $value): MessageInterface $name = $payload['name'] ?? null; if (!isset($name) || !is_string($name)) { - throw new InvalidArgumentException('Handler name must be string. Got ' . get_debug_type($name) . '.'); + throw new InvalidArgumentException('Handler name must be a string. Got ' . get_debug_type($name) . '.'); } $meta = $payload['meta'] ?? []; if (!is_array($meta)) { - throw new InvalidArgumentException('Metadata must be array. Got ' . get_debug_type($meta) . '.'); + throw new InvalidArgumentException('Metadata must be an array. Got ' . get_debug_type($meta) . '.'); } $envelopes = []; @@ -51,7 +55,7 @@ public function unserialize(string $value): MessageInterface } $meta[EnvelopeInterface::ENVELOPE_STACK_KEY] = []; - $class = $payload['class'] ?? Message::class; + $class = $payload['meta']['message-class'] ?? Message::class; if (!is_subclass_of($class, MessageInterface::class)) { $class = Message::class; } diff --git a/tests/Unit/Message/JsonMessageSerializerTest.php b/tests/Unit/Message/JsonMessageSerializerTest.php index 27efc892..7c583ba8 100644 --- a/tests/Unit/Message/JsonMessageSerializerTest.php +++ b/tests/Unit/Message/JsonMessageSerializerTest.php @@ -46,7 +46,7 @@ public function testMetadataFormat(mixed $meta): void $payload = ['name' => 'handler', 'data' => 'test', 'meta' => $meta]; $serializer = $this->createSerializer(); - $this->expectExceptionMessage(sprintf('Metadata must be array. Got %s.', get_debug_type($meta))); + $this->expectExceptionMessage(sprintf('Metadata must be an array. Got %s.', get_debug_type($meta))); $this->expectException(InvalidArgumentException::class); $serializer->unserialize(json_encode($payload)); } @@ -115,7 +115,7 @@ public function testSerialize(): void $json = $serializer->serialize($message); $this->assertEquals( - '{"name":"handler","data":"test","meta":[],"class":"Yiisoft\\\\Queue\\\\Message\\\\Message"}', + '{"name":"handler","data":"test","meta":{"message-class":"Yiisoft\\\\Queue\\\\Message\\\\Message"}}', $json, ); } @@ -131,7 +131,7 @@ public function testSerializeEnvelopeStack(): void $this->assertEquals( sprintf( - '{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id"},"class":"%s"}', + '{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id","message-class":"%s"}}', str_replace('\\', '\\\\', IdEnvelope::class), IdEnvelope::MESSAGE_ID_KEY, str_replace('\\', '\\\\', Message::class), @@ -148,11 +148,13 @@ public function testSerializeEnvelopeStack(): void IdEnvelope::class, ], IdEnvelope::MESSAGE_ID_KEY => 'test-id', + "message-class" => Message::class, ], $message->getMetadata()); $this->assertEquals([ EnvelopeInterface::ENVELOPE_STACK_KEY => [], IdEnvelope::MESSAGE_ID_KEY => 'test-id', + "message-class" => Message::class, ], $message->getMessage()->getMetadata()); } From 0c6f28f73a547d88883fbf3111df21327d8a5fcb Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 18 Sep 2024 14:13:08 +0000 Subject: [PATCH 5/7] Apply fixes from StyleCI --- tests/Unit/Message/JsonMessageSerializerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Message/JsonMessageSerializerTest.php b/tests/Unit/Message/JsonMessageSerializerTest.php index 7c583ba8..18bc5ed9 100644 --- a/tests/Unit/Message/JsonMessageSerializerTest.php +++ b/tests/Unit/Message/JsonMessageSerializerTest.php @@ -148,13 +148,13 @@ public function testSerializeEnvelopeStack(): void IdEnvelope::class, ], IdEnvelope::MESSAGE_ID_KEY => 'test-id', - "message-class" => Message::class, + 'message-class' => Message::class, ], $message->getMetadata()); $this->assertEquals([ EnvelopeInterface::ENVELOPE_STACK_KEY => [], IdEnvelope::MESSAGE_ID_KEY => 'test-id', - "message-class" => Message::class, + 'message-class' => Message::class, ], $message->getMessage()->getMetadata()); } From 4c7b0f804bb0b0514e98e4401439bfd316993a12 Mon Sep 17 00:00:00 2001 From: viktorprogger Date: Sun, 24 Nov 2024 20:03:41 +0500 Subject: [PATCH 6/7] Make JsonMessageSerializer::unserialize() faster when unserializable message class is the default one --- src/Message/JsonMessageSerializer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Message/JsonMessageSerializer.php b/src/Message/JsonMessageSerializer.php index 277b182a..d9b3a92e 100644 --- a/src/Message/JsonMessageSerializer.php +++ b/src/Message/JsonMessageSerializer.php @@ -56,7 +56,8 @@ public function unserialize(string $value): MessageInterface $meta[EnvelopeInterface::ENVELOPE_STACK_KEY] = []; $class = $payload['meta']['message-class'] ?? Message::class; - if (!is_subclass_of($class, MessageInterface::class)) { + // Don't check subclasses when it's a default class: that's faster + if ($class !== Message::class && !is_subclass_of($class, MessageInterface::class)) { $class = Message::class; } From 981adee982d75d28741648a887332652cec93277 Mon Sep 17 00:00:00 2001 From: viktorprogger Date: Sun, 24 Nov 2024 20:06:40 +0500 Subject: [PATCH 7/7] Unify the EnvelopeTrait::fromData() method body --- src/Message/EnvelopeTrait.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Message/EnvelopeTrait.php b/src/Message/EnvelopeTrait.php index bfe257a2..e8d19d65 100644 --- a/src/Message/EnvelopeTrait.php +++ b/src/Message/EnvelopeTrait.php @@ -15,9 +15,7 @@ abstract public static function fromMessage(MessageInterface $message): self; public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface { - $message = new Message($handlerName, $data, $metadata); - - return self::fromMessage($message); + return self::fromMessage(Message::fromData($handlerName, $data, $metadata)); } public function getMessage(): MessageInterface