Skip to content

Commit 1d62380

Browse files
authored
Require non-empty message type (#328)
1 parent ecec699 commit 1d62380

11 files changed

Lines changed: 75 additions & 31 deletions

File tree

docs/guide/en/message-handler-advanced.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Handler definitions are configured in:
1515
### Handlers mapped by short message type
1616

1717
Use a short stable message type instead of a PHP class name. That decoupling would allow you to refactor the code and handle the message with external handler.
18-
Define a dedicated message class where `getType()` returns that type:
18+
Define a dedicated message class where `getType()` returns that type. The type must be a non-empty string:
1919

2020
```php
2121
use Yiisoft\Queue\Message\Message;

docs/guide/en/messages-and-handlers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ new SendEmailMessage('user@example.com', 'Welcome', 'Thank you for registering.'
8989

9090
The message has:
9191

92-
- A **message type** — a string used by the worker to look up the correct handler.
92+
- A **message type** — a non-empty string used by the worker to look up the correct handler.
9393
- A **data payload** — typed properties serialized via `getPayload()`. Must contain only `null`, scalars (`bool`, `int`, `float`, `string`), or arrays composed of the same types recursively.
9494

9595
The message has no business logic, no dependencies. It is a value object — a typed data wrapper.

docs/guide/en/migrating-from-yii2-queue.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ There was a concept in [yiisoft/yii2-queue] called `Job`: you had to push it to
1818
being consumed. In the new package, it is divided into two different concepts: a message and a handler.
1919

2020
- A `Message` is a class implementing `MessageInterface`. It contains two types of data:
21-
- Type. The worker uses it to find the right handler for a message.
21+
- Type. A non-empty string. The worker uses it to find the right handler for a message.
2222
- Payload. Any serializable data that should be used by the message handler.
2323

2424
All the message payload is fully serializable (that means message `payload` must be serializable too). It allows you to

src/Message/ClassResolver/MessageClassResolverInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ interface MessageClassResolverInterface
1414
/**
1515
* Returns the message class for the given type, or `null` if the type is not registered.
1616
*
17-
* @param string $type Message type.
17+
* @param string $type Message type. Must be a non-empty string.
1818
*
1919
* @return string|null Message class, or `null` if the type is not registered.
2020
*
21+
* @psalm-param non-empty-string $type
2122
* @psalm-return class-string<MessageInterface>|null
2223
*/
2324
public function resolve(string $type): ?string;

src/Message/GenericMessage.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Yiisoft\Queue\Message;
66

7+
use InvalidArgumentException;
8+
79
/**
810
* A general-purpose immutable {@see MessageInterface} implementation that holds a message type and its payload data.
911
*
@@ -14,16 +16,24 @@
1416
final class GenericMessage extends Message
1517
{
1618
/**
17-
* @param string $type A message type used to resolve the handler.
19+
* @param string $type A message type used to resolve the handler. Must be a non-empty string.
1820
* @param bool|int|float|string|array|null $payload Message payload data. Must contain only `null`, scalars (`bool`,
1921
* `int`, `float`, `string`), or arrays composed of the same types recursively.
2022
*
23+
* @psalm-param non-empty-string $type
2124
* @psalm-param MessagePayload $payload
2225
*/
2326
public function __construct(
2427
private readonly string $type,
2528
private readonly bool|int|float|string|array|null $payload,
26-
) {}
29+
) {
30+
/**
31+
* @psalm-suppress TypeDoesNotContainType Guard against an empty type passed without static analysis.
32+
*/
33+
if ($this->type === '') {
34+
throw new InvalidArgumentException('Message type must be a non-empty string.');
35+
}
36+
}
2737

2838
public static function fromPayload(string $type, bool|int|float|string|array|null $payload): static
2939
{

src/Message/Handler/HandlerResolver.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Yiisoft\Queue\Message\Handler;
66

7-
use LogicException;
87
use Psr\Container\ContainerExceptionInterface;
98
use Psr\Container\ContainerInterface;
109
use Yiisoft\Injector\Injector;
@@ -51,18 +50,16 @@ public function __construct(
5150
/**
5251
* Get a handler for the given message type.
5352
*
54-
* @param string $messageType Message type.
53+
* @param string $messageType Message type. Must be a non-empty string.
54+
*
55+
* @psalm-param non-empty-string $messageType
5556
*
5657
* @throws HandlerNotFoundException If no handler exists for the message type.
5758
* @throws InvalidHandlerConfigurationException If the handler definition is configured incorrectly.
5859
* @throws ContainerExceptionInterface Error while retrieving the entry from container.
5960
*/
6061
public function resolve(string $messageType): HandlerInterface
6162
{
62-
if ($messageType === '') {
63-
throw new LogicException('Message type cannot be empty.');
64-
}
65-
6663
if (array_key_exists($messageType, $this->cache)) {
6764
return $this->cache[$messageType];
6865
}

src/Message/MessageInterface.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ interface MessageInterface
1515
/**
1616
* Creates a new message instance from the given type and payload data.
1717
*
18-
* @param string $type Message type.
18+
* @param string $type Message type. Must be a non-empty string.
1919
* @param bool|int|float|string|array|null $payload Message payload data. Must contain only `null`, scalars (`bool`,
2020
* `int`, `float`, `string`), or arrays composed of the same types recursively.
2121
*
22+
* @psalm-param non-empty-string $type
2223
* @psalm-param MessagePayload $payload
2324
*
2425
* @return static Instance of the called class with the given type and payload.
@@ -28,7 +29,9 @@ public static function fromPayload(string $type, bool|int|float|string|array|nul
2829
/**
2930
* Returns message type.
3031
*
31-
* @return string Message type.
32+
* @return string Message type. Always a non-empty string.
33+
*
34+
* @psalm-return non-empty-string
3235
*/
3336
public function getType(): string;
3437

src/Message/Serializer/MessageSerializer.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ public function unserialize(string $value): MessageInterface
5757
$data = $this->encoder->decode($value);
5858

5959
if (!is_array($data)) {
60-
throw new MessageSerializerException('Decoded data must be array. Got ' . get_debug_type($data) . '.');
60+
throw new MessageSerializerException('Decoded data must be an array. Got ' . get_debug_type($data) . '.');
6161
}
6262

6363
$type = $data['type'] ?? null;
64-
if (!isset($type) || !is_string($type)) {
65-
throw new MessageSerializerException('Message type must be a string. Got ' . get_debug_type($type) . '.');
64+
if (!is_string($type)) {
65+
throw new MessageSerializerException('Message type must be a non-empty string. Got ' . get_debug_type($type) . '.');
66+
}
67+
if ($type === '') {
68+
throw new MessageSerializerException('Message type must be a non-empty string. Got empty string.');
6669
}
6770

6871
$meta = $data['meta'] ?? [];
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Queue\Tests\Unit\Message;
6+
7+
use InvalidArgumentException;
8+
use PHPUnit\Framework\TestCase;
9+
use Yiisoft\Queue\Message\GenericMessage;
10+
11+
final class GenericMessageTest extends TestCase
12+
{
13+
public function testConstructorThrowsWhenTypeIsEmpty(): void
14+
{
15+
$this->expectException(InvalidArgumentException::class);
16+
$this->expectExceptionMessage('Message type must be a non-empty string.');
17+
18+
new GenericMessage('', null);
19+
}
20+
21+
public function testFromPayloadThrowsWhenTypeIsEmpty(): void
22+
{
23+
$this->expectException(InvalidArgumentException::class);
24+
$this->expectExceptionMessage('Message type must be a non-empty string.');
25+
26+
GenericMessage::fromPayload('', null);
27+
}
28+
}

tests/Unit/Message/Handler/Resolver/HandlerResolverTest.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Yiisoft\Queue\Tests\Unit\Message\Handler\Resolver;
66

7-
use LogicException;
87
use PHPUnit\Framework\Attributes\DataProvider;
98
use PHPUnit\Framework\TestCase;
109
use Yiisoft\Test\Support\Container\SimpleContainer;
@@ -182,17 +181,6 @@ public function handle(): void {}
182181

183182
$resolver->resolve('invalid');
184183
}
185-
186-
public function testResolveThrowsWhenMessageTypeIsEmpty(): void
187-
{
188-
$this->expectException(LogicException::class);
189-
$this->expectExceptionMessage('Message type cannot be empty.');
190-
191-
$container = new SimpleContainer();
192-
$resolver = new HandlerResolver([], $container);
193-
194-
$resolver->resolve('');
195-
}
196184
}
197185

198186
function namedFunctionHandler(MessageInterface $message): void

0 commit comments

Comments
 (0)