Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to restore original pushed message class on consume #214

Merged
merged 9 commits into from
Nov 24, 2024
12 changes: 12 additions & 0 deletions src/Message/EnvelopeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ trait EnvelopeTrait
{
private MessageInterface $message;

/**
* A mirror of {@see MessageInterface::fromData()}
viktorprogger marked this conversation as resolved.
Show resolved Hide resolved
*/
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;
Expand Down
35 changes: 24 additions & 11 deletions src/Message/JsonMessageSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function serialize(MessageInterface $message): string
'name' => $message->getHandlerName(),
'data' => $message->getData(),
'meta' => $message->getMetadata(),
'class' => $message instanceof EnvelopeInterface ? $message->getMessage()::class : $message::class,
viktorprogger marked this conversation as resolved.
Show resolved Hide resolved
];

return json_encode($payload, JSON_THROW_ON_ERROR);
Expand All @@ -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) . '.');
viktorprogger marked this conversation as resolved.
Show resolved Hide resolved
}

$meta = $payload['meta'] ?? [];
if (!is_array($meta)) {
throw new InvalidArgumentException('Metadata must be array. Got ' . get_debug_type($meta) . '.');
viktorprogger marked this conversation as resolved.
Show resolved Hide resolved
}

// 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<MessageInterface> $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;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Message/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/Message/MessageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

interface MessageInterface
{
public static function fromData(string $handlerName, mixed $data, array $metadata = []): self;

/**
* Returns handler name.
*
Expand Down
36 changes: 29 additions & 7 deletions tests/Unit/Message/JsonMessageSerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)));
Expand All @@ -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 => [
Expand Down Expand Up @@ -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"}',
viktorprogger marked this conversation as resolved.
Show resolved Hide resolved
$json,
);
}
Expand All @@ -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,
);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/Support/TestMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Queue\Tests\Unit\Support;

use Yiisoft\Queue\Message\MessageInterface;

final class TestMessage implements MessageInterface
{
public static function fromData(string $handlerName, mixed $data, array $metadata = []): MessageInterface
{
return new self();
}

public function getHandlerName(): string
{
return 'test';
}

public function getData(): mixed
{
return null;
}

public function getMetadata(): array
{
return [];
}
}
Loading