Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8700123
Convert envelope trait to abstract class
viktorprogger Dec 15, 2024
3ecc37e
Require yiisoft/arrays package
viktorprogger Dec 15, 2024
bff438d
Merge branch 'master' into convert-envelope-trait-to-abstract-class
viktorprogger Feb 14, 2025
e4e4723
Merge remote-tracking branch 'origin/master' into convert-envelope-tr…
viktorprogger Feb 14, 2025
95b6ea4
Bugfixes
viktorprogger May 4, 2025
666d650
Apply fixes from StyleCI
StyleCIBot May 4, 2025
c6b758d
Rename FailureEnvelope::$failureMeta
viktorprogger May 4, 2025
7516793
Add more typing
viktorprogger May 4, 2025
198ea47
Merge remote-tracking branch 'origin/master' into convert-envelope-tr…
viktorprogger May 8, 2025
d0deac6
Fix envelope static constructors return types
viktorprogger May 8, 2025
fefb5cb
Add tests for the new code
viktorprogger May 8, 2025
1cdcfe1
Apply fixes from StyleCI
StyleCIBot May 8, 2025
3ec891f
Re-run checks
viktorprogger May 8, 2025
b63f5e7
Fix fromMessage() return types in implementations
viktorprogger May 8, 2025
f8c47ba
Apply fixes from StyleCI
StyleCIBot May 8, 2025
4370fa0
Ignore psalm false-positives
viktorprogger May 8, 2025
f3f388b
Memorise metadata in envelopes
viktorprogger May 9, 2025
cab6ab1
Get message metadata just once
viktorprogger Jul 13, 2025
d676a3c
Address performance issues
viktorprogger Jul 13, 2025
2340afc
Merge branch 'master' into convert-envelope-trait-to-abstract-class
viktorprogger Jul 14, 2025
5d9cd41
Merge branch 'master' into convert-envelope-trait-to-abstract-class
viktorprogger Jul 14, 2025
e7acc32
Fix IdEnvelope after master merge
viktorprogger Jul 14, 2025
0c1292d
Get back 100 000 bench revisions after master merge
viktorprogger Jul 14, 2025
076e8dd
Apply fixes from StyleCI
StyleCIBot Jul 14, 2025
209ba7b
Merge branch 'refs/heads/master' into convert-envelope-trait-to-abstr…
viktorprogger Jul 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"psr/container": "^1.0 || ^2.0",
"psr/log": "^2.0 || ^3.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"yiisoft/arrays": "^3.1",
"yiisoft/definitions": "^3.3.1",
"yiisoft/factory": "^1.3",
"yiisoft/friendly-exception": "^1.0",
Expand Down
2 changes: 1 addition & 1 deletion phpbench.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema":"./vendor/phpbench/phpbench/phpbench.schema.json",
"runner.bootstrap": "vendor/autoload.php",
"runner.path": "tests/Benchmark",
"runner.revs": 1000,
"runner.revs": 100000,
"runner.iterations": 5,
"runner.warmup": 5
}
63 changes: 63 additions & 0 deletions src/Message/Envelope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Queue\Message;

abstract class Envelope implements EnvelopeInterface
{
private ?array $metadata = null;

public function __construct(protected MessageInterface $message)
{
}

/** @psalm-suppress MoreSpecificReturnType */
public static function fromData(string $handlerName, mixed $data, array $metadata = []): static
{
/** @psalm-suppress LessSpecificReturnStatement */
return static::fromMessage(Message::fromData($handlerName, $data, $metadata));
}

public function getMessage(): MessageInterface
{
return $this->message;
}

public function getHandlerName(): string
{
return $this->message->getHandlerName();
}

public function getData(): mixed
{
return $this->message->getData();
}

public function getMetadata(): array
{
if ($this->metadata === null) {
$messageMeta = $this->message->getMetadata();

$stack = $messageMeta[EnvelopeInterface::ENVELOPE_STACK_KEY] ?? [];
if (!is_array($stack)) {
$stack = [];

Check warning on line 44 in src/Message/Envelope.php

View check run for this annotation

Codecov / codecov/patch

src/Message/Envelope.php#L44

Added line #L44 was not covered by tests
}

$this->metadata = array_merge(
$messageMeta,
[
EnvelopeInterface::ENVELOPE_STACK_KEY => array_merge(
$stack,
[static::class],
),
],
$this->getEnvelopeMetadata(),
);
}

return $this->metadata;
}

abstract protected function getEnvelopeMetadata(): array;
}
4 changes: 1 addition & 3 deletions src/Message/EnvelopeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ interface EnvelopeInterface extends MessageInterface
/** @psalm-suppress MissingClassConstType */
public const ENVELOPE_STACK_KEY = 'envelopes';

public static function fromMessage(MessageInterface $message): self;
public static function fromMessage(MessageInterface $message): static;

public function getMessage(): MessageInterface;

public function withMessage(MessageInterface $message): self;
}
62 changes: 0 additions & 62 deletions src/Message/EnvelopeTrait.php

This file was deleted.

47 changes: 20 additions & 27 deletions src/Message/IdEnvelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,42 @@
namespace Yiisoft\Queue\Message;

/**
* ID envelope allows identifying a message.
* ID envelope allows to identify a message.
*/
final class IdEnvelope implements EnvelopeInterface
final class IdEnvelope extends Envelope
{
use EnvelopeTrait;

public const MESSAGE_ID_KEY = 'yii-message-id';

public function __construct(
MessageInterface $message,
private readonly string|int|null $id = null,
protected MessageInterface $message,
private readonly string|int|null $id,
) {
$this->message = $message;
}

public static function fromMessage(MessageInterface $message): self
public static function fromMessage(MessageInterface $message): static
{
return new self($message, self::getIdFromMessage($message));
/** @var mixed $rawId */
$rawId = $message->getMetadata()[self::MESSAGE_ID_KEY] ?? null;

/** @var int|string|null $id */
$id = match (true) {
$rawId === null => null, // don't remove this branch: it's important for compute speed
is_string($rawId) => $rawId,
is_int($rawId) => $rawId,
is_object($rawId) && method_exists($rawId, '__toString') => (string)$rawId,
default => null,
};

return new self($message, $id);
}

public function getId(): string|int|null
{
return $this->id ?? self::getIdFromMessage($this->message);
return $this->id;
}

private function getEnvelopeMetadata(): array
protected function getEnvelopeMetadata(): array
{
return [self::MESSAGE_ID_KEY => $this->getId()];
}

private static function getIdFromMessage(MessageInterface $message): string|int|null
{
$id = $message->getMetadata()[self::MESSAGE_ID_KEY] ?? null;
if ($id instanceof \Stringable) {
$id = (string) $id;
}

// We don't throw an error as this value could come from external sources,
// and we should process the message either way
if (!is_string($id) && !is_int($id)) {
return null;
}

return $id;
}
}
9 changes: 1 addition & 8 deletions src/Message/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
final class Message implements MessageInterface
{
/**
* @param string $handlerName A name of a handler which should handle this message.
* @param mixed $data Message data, encodable by a queue adapter
* @param array $metadata Message metadata, encodable by a queue adapter
*/
Expand Down Expand Up @@ -36,12 +37,4 @@ public function getMetadata(): array
{
return $this->metadata;
}

public function withMetadata(array $metadata): self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

@viktorprogger viktorprogger May 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any real-world use cases. Do you?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Message handlers may add metadata into the message

Copy link
Contributor Author

@viktorprogger viktorprogger Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They should wrap it into an envelope then.

{
$instance = clone $this;
$instance->metadata = $metadata;

return $instance;
}
}
6 changes: 0 additions & 6 deletions src/Message/MessageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,16 @@ public static function fromData(string $handlerName, mixed $data, array $metadat

/**
* Returns handler name.
*
* @return string
*/
public function getHandlerName(): string;

/**
* Returns payload data.
*
* @return mixed
*/
public function getData(): mixed;

/**
* Returns message metadata: timings, attempts count, metrics, etc.
*
* @return array
*/
public function getMetadata(): array;
}
30 changes: 14 additions & 16 deletions src/Middleware/FailureHandling/FailureEnvelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,33 @@

namespace Yiisoft\Queue\Middleware\FailureHandling;

use Yiisoft\Queue\Message\EnvelopeInterface;
use Yiisoft\Queue\Message\EnvelopeTrait;
use Yiisoft\Arrays\ArrayHelper;
use Yiisoft\Queue\Message\Envelope;
use Yiisoft\Queue\Message\MessageInterface;

final class FailureEnvelope implements EnvelopeInterface
final class FailureEnvelope extends Envelope
{
use EnvelopeTrait {
getMetadata as getMetadataParent;
}

public const FAILURE_META_KEY = 'failure-meta';

public function __construct(
MessageInterface $message,
private readonly array $meta = [],
protected MessageInterface $message,
private readonly array $metadata = [],
) {
$this->message = $message;
}

public static function fromMessage(MessageInterface $message): self
public static function fromMessage(MessageInterface $message): static
{
return new self($message, $message->getMetadata()[self::FAILURE_META_KEY] ?? []);
/** @var array $metadata */
$metadata = $message->getMetadata()[self::FAILURE_META_KEY] ?? [];

return new self($message, $metadata);
}

public function getMetadata(): array
protected function getEnvelopeMetadata(): array
{
$meta = $this->getMetadataParent();
$meta[self::FAILURE_META_KEY] = array_merge($meta[self::FAILURE_META_KEY] ?? [], $this->meta);
/** @var array $metadata */
$metadata = $this->message->getMetadata()[self::FAILURE_META_KEY] ?? [];

return $meta;
return [self::FAILURE_META_KEY => ArrayHelper::merge($metadata, $this->metadata)];
}
}
17 changes: 8 additions & 9 deletions tests/App/DummyEnvelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@

namespace Yiisoft\Queue\Tests\App;

use Yiisoft\Queue\Message\EnvelopeInterface;
use Yiisoft\Queue\Message\EnvelopeTrait;
use Yiisoft\Queue\Message\Envelope;
use Yiisoft\Queue\Message\MessageInterface;

final class DummyEnvelope implements EnvelopeInterface
final class DummyEnvelope extends Envelope
{
use EnvelopeTrait;

public static function fromMessage(MessageInterface $message): self
public static function fromMessage(MessageInterface $message): static
{
$instance = new self();
$instance->message = $message;
return new self($message);
}

return $instance;
protected function getEnvelopeMetadata(): array
{
return [];
}
}
23 changes: 0 additions & 23 deletions tests/Unit/Message/EnvelopeTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@
namespace Yiisoft\Queue\Tests\Unit\Message;

use PHPUnit\Framework\TestCase;
use Yiisoft\Queue\Message\Message;
use Yiisoft\Queue\Tests\App\DummyEnvelope;

final class EnvelopeTraitTest extends TestCase
{
private function createTestEnvelope(): DummyEnvelope
{
return new DummyEnvelope();
}

public function testFromData(): void
{
$handlerName = 'test-handler';
Expand All @@ -29,21 +23,4 @@ public function testFromData(): void
$this->assertArrayHasKey('meta', $envelope->getMetadata());
$this->assertSame('data', $envelope->getMetadata()['meta']);
}

public function testWithMessage(): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep it

Copy link
Contributor Author

@viktorprogger viktorprogger Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this better than FooEnvelope::fromMessage($message) or new FooEnvelope()?

{
$originalMessage = new Message('original-handler', 'original-data');
$newMessage = new Message('new-handler', 'new-data');

$envelope = $this->createTestEnvelope();
$envelope = $envelope->withMessage($originalMessage);

$this->assertSame($originalMessage, $envelope->getMessage());

$newEnvelope = $envelope->withMessage($newMessage);

$this->assertNotSame($envelope, $newEnvelope);
$this->assertSame($newMessage, $newEnvelope->getMessage());
$this->assertSame($originalMessage, $envelope->getMessage());
}
}
3 changes: 0 additions & 3 deletions tests/Unit/Message/IdEnvelopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,6 @@ public function testFromData(): void
$data = ['key' => 'value'];
$metadata = ['meta' => 'data', IdEnvelope::MESSAGE_ID_KEY => 'test-id'];

/**
* @var IdEnvelope $envelope
*/
$envelope = IdEnvelope::fromData($handlerName, $data, $metadata);

$this->assertInstanceOf(IdEnvelope::class, $envelope);
Expand Down
Loading
Loading