Skip to content

Commit af0fb00

Browse files
Allow to restore original pushed message class on consume (#214)
* Allow to restore original pushed message class on consume * Apply fixes from StyleCI * Apply Rector changes (CI) * Move message class name to metadata and fix tests * Apply fixes from StyleCI * Make JsonMessageSerializer::unserialize() faster when unserializable message class is the default one * Unify the EnvelopeTrait::fromData() method body --------- Co-authored-by: StyleCI Bot <bot@styleci.io> Co-authored-by: viktorprogger <viktorprogger@users.noreply.github.com>
1 parent b337fdd commit af0fb00

6 files changed

Lines changed: 109 additions & 20 deletions

File tree

src/Message/EnvelopeTrait.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ trait EnvelopeTrait
88
{
99
private MessageInterface $message;
1010

11+
/**
12+
* A mirror of {@see MessageInterface::fromData()}
13+
*/
14+
abstract public static function fromMessage(MessageInterface $message): self;
15+
16+
public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface
17+
{
18+
return self::fromMessage(Message::fromData($handlerName, $data, $metadata));
19+
}
20+
1121
public function getMessage(): MessageInterface
1222
{
1323
return $this->message;

src/Message/JsonMessageSerializer.php

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public function serialize(MessageInterface $message): string
1919
'data' => $message->getData(),
2020
'meta' => $message->getMetadata(),
2121
];
22+
if (!isset($payload['meta']['message-class'])) {
23+
$payload['meta']['message-class'] = $message instanceof EnvelopeInterface
24+
? $message->getMessage()::class
25+
: $message::class;
26+
}
2227

2328
return json_encode($payload, JSON_THROW_ON_ERROR);
2429
}
@@ -34,25 +39,38 @@ public function unserialize(string $value): MessageInterface
3439
throw new InvalidArgumentException('Payload must be array. Got ' . get_debug_type($payload) . '.');
3540
}
3641

42+
$name = $payload['name'] ?? null;
43+
if (!isset($name) || !is_string($name)) {
44+
throw new InvalidArgumentException('Handler name must be a string. Got ' . get_debug_type($name) . '.');
45+
}
46+
3747
$meta = $payload['meta'] ?? [];
3848
if (!is_array($meta)) {
39-
throw new InvalidArgumentException('Metadata must be array. Got ' . get_debug_type($meta) . '.');
49+
throw new InvalidArgumentException('Metadata must be an array. Got ' . get_debug_type($meta) . '.');
4050
}
4151

42-
// TODO: will be removed later
43-
$message = new Message($payload['name'] ?? '$name', $payload['data'] ?? null, $meta);
44-
52+
$envelopes = [];
4553
if (isset($meta[EnvelopeInterface::ENVELOPE_STACK_KEY]) && is_array($meta[EnvelopeInterface::ENVELOPE_STACK_KEY])) {
46-
$message = $message->withMetadata(
47-
array_merge($message->getMetadata(), [EnvelopeInterface::ENVELOPE_STACK_KEY => []]),
48-
);
49-
foreach ($meta[EnvelopeInterface::ENVELOPE_STACK_KEY] as $envelope) {
50-
if (is_string($envelope) && class_exists($envelope) && is_subclass_of($envelope, EnvelopeInterface::class)) {
51-
$message = $envelope::fromMessage($message);
52-
}
53-
}
54+
$envelopes = $meta[EnvelopeInterface::ENVELOPE_STACK_KEY];
5455
}
56+
$meta[EnvelopeInterface::ENVELOPE_STACK_KEY] = [];
5557

58+
$class = $payload['meta']['message-class'] ?? Message::class;
59+
// Don't check subclasses when it's a default class: that's faster
60+
if ($class !== Message::class && !is_subclass_of($class, MessageInterface::class)) {
61+
$class = Message::class;
62+
}
63+
64+
/**
65+
* @var class-string<MessageInterface> $class
66+
*/
67+
$message = $class::fromData($name, $payload['data'] ?? null, $meta);
68+
69+
foreach ($envelopes as $envelope) {
70+
if (is_string($envelope) && class_exists($envelope) && is_subclass_of($envelope, EnvelopeInterface::class)) {
71+
$message = $envelope::fromMessage($message);
72+
}
73+
}
5674

5775
return $message;
5876
}

src/Message/Message.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public function __construct(
1818
) {
1919
}
2020

21+
public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface
22+
{
23+
return new self($handlerName, $data, $metadata);
24+
}
25+
2126
public function getHandlerName(): string
2227
{
2328
return $this->handlerName;

src/Message/MessageInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
interface MessageInterface
88
{
9+
public static function fromData(string $handlerName, mixed $data, array $metadata = []): self;
10+
911
/**
1012
* Returns handler name.
1113
*

tests/Unit/Message/JsonMessageSerializerTest.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Yiisoft\Queue\Message\JsonMessageSerializer;
1212
use Yiisoft\Queue\Message\Message;
1313
use Yiisoft\Queue\Message\MessageInterface;
14+
use Yiisoft\Queue\Tests\Unit\Support\TestMessage;
1415

1516
/**
1617
* Testing message serialization options
@@ -42,10 +43,10 @@ public static function dataUnsupportedPayloadFormat(): iterable
4243
*/
4344
public function testMetadataFormat(mixed $meta): void
4445
{
45-
$payload = ['data' => 'test', 'meta' => $meta];
46+
$payload = ['name' => 'handler', 'data' => 'test', 'meta' => $meta];
4647
$serializer = $this->createSerializer();
4748

48-
$this->expectExceptionMessage(sprintf('Metadata must be array. Got %s.', get_debug_type($meta)));
49+
$this->expectExceptionMessage(sprintf('Metadata must be an array. Got %s.', get_debug_type($meta)));
4950
$this->expectException(InvalidArgumentException::class);
5051
$serializer->unserialize(json_encode($payload));
5152
}
@@ -59,31 +60,32 @@ public static function dataUnsupportedMetadataFormat(): iterable
5960

6061
public function testUnserializeFromData(): void
6162
{
62-
$payload = ['data' => 'test'];
63+
$payload = ['name' => 'handler', 'data' => 'test'];
6364
$serializer = $this->createSerializer();
6465

6566
$message = $serializer->unserialize(json_encode($payload));
6667

6768
$this->assertInstanceOf(MessageInterface::class, $message);
6869
$this->assertEquals($payload['data'], $message->getData());
69-
$this->assertEquals([], $message->getMetadata());
70+
$this->assertEquals([EnvelopeInterface::ENVELOPE_STACK_KEY => []], $message->getMetadata());
7071
}
7172

7273
public function testUnserializeWithMetadata(): void
7374
{
74-
$payload = ['data' => 'test', 'meta' => ['int' => 1, 'str' => 'string', 'bool' => true]];
75+
$payload = ['name' => 'handler', 'data' => 'test', 'meta' => ['int' => 1, 'str' => 'string', 'bool' => true]];
7576
$serializer = $this->createSerializer();
7677

7778
$message = $serializer->unserialize(json_encode($payload));
7879

7980
$this->assertInstanceOf(MessageInterface::class, $message);
8081
$this->assertEquals($payload['data'], $message->getData());
81-
$this->assertEquals(['int' => 1, 'str' => 'string', 'bool' => true], $message->getMetadata());
82+
$this->assertEquals(['int' => 1, 'str' => 'string', 'bool' => true, EnvelopeInterface::ENVELOPE_STACK_KEY => []], $message->getMetadata());
8283
}
8384

8485
public function testUnserializeEnvelopeStack(): void
8586
{
8687
$payload = [
88+
'name' => 'handler',
8789
'data' => 'test',
8890
'meta' => [
8991
EnvelopeInterface::ENVELOPE_STACK_KEY => [
@@ -113,7 +115,7 @@ public function testSerialize(): void
113115
$json = $serializer->serialize($message);
114116

115117
$this->assertEquals(
116-
'{"name":"handler","data":"test","meta":[]}',
118+
'{"name":"handler","data":"test","meta":{"message-class":"Yiisoft\\\\Queue\\\\Message\\\\Message"}}',
117119
$json,
118120
);
119121
}
@@ -129,9 +131,10 @@ public function testSerializeEnvelopeStack(): void
129131

130132
$this->assertEquals(
131133
sprintf(
132-
'{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id"}}',
134+
'{"name":"handler","data":"test","meta":{"envelopes":["%s"],"%s":"test-id","message-class":"%s"}}',
133135
str_replace('\\', '\\\\', IdEnvelope::class),
134136
IdEnvelope::MESSAGE_ID_KEY,
137+
str_replace('\\', '\\\\', Message::class),
135138
),
136139
$json,
137140
);
@@ -145,14 +148,35 @@ public function testSerializeEnvelopeStack(): void
145148
IdEnvelope::class,
146149
],
147150
IdEnvelope::MESSAGE_ID_KEY => 'test-id',
151+
'message-class' => Message::class,
148152
], $message->getMetadata());
149153

150154
$this->assertEquals([
151155
EnvelopeInterface::ENVELOPE_STACK_KEY => [],
152156
IdEnvelope::MESSAGE_ID_KEY => 'test-id',
157+
'message-class' => Message::class,
153158
], $message->getMessage()->getMetadata());
154159
}
155160

161+
public function testRestoreOriginalMessageClass(): void
162+
{
163+
$message = new TestMessage();
164+
$serializer = $this->createSerializer();
165+
$serializer->unserialize($serializer->serialize($message));
166+
167+
$this->assertInstanceOf(TestMessage::class, $message);
168+
}
169+
170+
public function testRestoreOriginalMessageClassWithEnvelope(): void
171+
{
172+
$message = new IdEnvelope(new TestMessage());
173+
$serializer = $this->createSerializer();
174+
$serializer->unserialize($serializer->serialize($message));
175+
176+
$this->assertInstanceOf(IdEnvelope::class, $message);
177+
$this->assertInstanceOf(TestMessage::class, $message->getMessage());
178+
}
179+
156180
private function createSerializer(): JsonMessageSerializer
157181
{
158182
return new JsonMessageSerializer();

tests/Unit/Support/TestMessage.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Queue\Tests\Unit\Support;
6+
7+
use Yiisoft\Queue\Message\MessageInterface;
8+
9+
final class TestMessage implements MessageInterface
10+
{
11+
public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface
12+
{
13+
return new self();
14+
}
15+
16+
public function getHandlerName(): string
17+
{
18+
return 'test';
19+
}
20+
21+
public function getData(): mixed
22+
{
23+
return null;
24+
}
25+
26+
public function getMetadata(): array
27+
{
28+
return [];
29+
}
30+
}

0 commit comments

Comments
 (0)