feature #29159 [Messenger] collect all stamps added on Envelope as collections (nicolas-grekas)
This PR was merged into the 4.2-dev branch.
Discussion
----------
[Messenger] collect all stamps added on Envelope as collections
| Q | A
| ------------- | ---
| Branch? | 4.2
| Bug fix? | no
| New feature? | yes
| BC breaks? | yes
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #29156
| License | MIT
| Doc PR | -
Late small BC break for Messenger:
* `Envelope::all()` takes a new optional `$stampFqcn` argument and returns the stamps for the specified FQCN, or all stamps by their class name
* `Envelope::get()` has been renamed `Envelope::last()`
This fixes the current behavior where we replace any previous stamp with the same type, which is unexpected to me as it silently loses data and more importantly blocks interesting use cases we're going to need in the near future.
Basically, that's the same as HTTP headers being allowed to exist several times: most of them make no sense as collections, but some are really useful as collections.
Commits
-------
2e9885922a
[Messenger] collect all stamps added on Envelope as collections
This commit is contained in:
commit
fb249f0b69
@ -33,6 +33,8 @@ CHANGELOG
|
||||
* `ActivationMiddlewareDecorator` has been renamed `ActivationMiddleware`
|
||||
* `AllowNoHandlerMiddleware` has been removed in favor of a new constructor argument on `HandleMessageMiddleware`
|
||||
* The `ContainerHandlerLocator`, `AbstractHandlerLocator`, `SenderLocator` and `AbstractSenderLocator` classes have been removed
|
||||
* `Envelope::all()` takes a new optional `$stampFqcn` argument and returns the stamps for the specified FQCN, or all stamps by their class name
|
||||
* `Envelope::get()` has been renamed `Envelope::last()`
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
@ -36,7 +36,7 @@ final class Envelope
|
||||
$this->message = $message;
|
||||
|
||||
foreach ($stamps as $stamp) {
|
||||
$this->stamps[\get_class($stamp)] = $stamp;
|
||||
$this->stamps[\get_class($stamp)][] = $stamp;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,22 +48,26 @@ final class Envelope
|
||||
$cloned = clone $this;
|
||||
|
||||
foreach ($stamps as $stamp) {
|
||||
$cloned->stamps[\get_class($stamp)] = $stamp;
|
||||
$cloned->stamps[\get_class($stamp)][] = $stamp;
|
||||
}
|
||||
|
||||
return $cloned;
|
||||
}
|
||||
|
||||
public function get(string $stampFqcn): ?StampInterface
|
||||
public function last(string $stampFqcn): ?StampInterface
|
||||
{
|
||||
return $this->stamps[$stampFqcn] ?? null;
|
||||
return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StampInterface[] indexed by fqcn
|
||||
* @return StampInterface[]|StampInterface[][] The stamps for the specified FQCN, or all stamps by their class name
|
||||
*/
|
||||
public function all(): array
|
||||
public function all(string $stampFqcn = null): array
|
||||
{
|
||||
if (null !== $stampFqcn) {
|
||||
return $this->stamps[$stampFqcn] ?? array();
|
||||
}
|
||||
|
||||
return $this->stamps;
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||
*/
|
||||
public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
if ($envelope->get(ReceivedStamp::class)) {
|
||||
if ($envelope->all(ReceivedStamp::class)) {
|
||||
// it's a received message, do not send it back
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class ValidationMiddleware implements MiddlewareInterface
|
||||
$message = $envelope->getMessage();
|
||||
$groups = null;
|
||||
/** @var ValidationStamp|null $validationStamp */
|
||||
if ($validationStamp = $envelope->get(ValidationStamp::class)) {
|
||||
if ($validationStamp = $envelope->last(ValidationStamp::class)) {
|
||||
$groups = $validationStamp->getGroups();
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ class EnvelopeTest extends TestCase
|
||||
|
||||
$this->assertSame($dummy, $envelope->getMessage());
|
||||
$this->assertArrayHasKey(ReceivedStamp::class, $stamps = $envelope->all());
|
||||
$this->assertSame($receivedStamp, $stamps[ReceivedStamp::class]);
|
||||
$this->assertSame($receivedStamp, $stamps[ReceivedStamp::class][0]);
|
||||
}
|
||||
|
||||
public function testWithReturnsNewInstance()
|
||||
@ -39,13 +39,13 @@ class EnvelopeTest extends TestCase
|
||||
$this->assertNotSame($envelope, $envelope->with(new ReceivedStamp()));
|
||||
}
|
||||
|
||||
public function testGet()
|
||||
public function testGetLast()
|
||||
{
|
||||
$receivedStamp = new ReceivedStamp();
|
||||
$envelope = new Envelope($dummy = new DummyMessage('dummy'), $receivedStamp);
|
||||
|
||||
$this->assertSame($receivedStamp, $envelope->get(ReceivedStamp::class));
|
||||
$this->assertNull($envelope->get(ValidationStamp::class));
|
||||
$this->assertSame($receivedStamp, $envelope->last(ReceivedStamp::class));
|
||||
$this->assertNull($envelope->last(ValidationStamp::class));
|
||||
}
|
||||
|
||||
public function testAll()
|
||||
@ -57,8 +57,8 @@ class EnvelopeTest extends TestCase
|
||||
|
||||
$stamps = $envelope->all();
|
||||
$this->assertArrayHasKey(ReceivedStamp::class, $stamps);
|
||||
$this->assertSame($receivedStamp, $stamps[ReceivedStamp::class]);
|
||||
$this->assertSame($receivedStamp, $stamps[ReceivedStamp::class][0]);
|
||||
$this->assertArrayHasKey(ValidationStamp::class, $stamps);
|
||||
$this->assertSame($validationStamp, $stamps[ValidationStamp::class]);
|
||||
$this->assertSame($validationStamp, $stamps[ValidationStamp::class][0]);
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class TraceableMessageBusTest extends TestCase
|
||||
$this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages());
|
||||
$this->assertArraySubset(array(
|
||||
'message' => $message,
|
||||
'stamps' => array($stamp),
|
||||
'stamps' => array(array($stamp)),
|
||||
'caller' => array(
|
||||
'name' => 'TraceableMessageBusTest.php',
|
||||
'file' => __FILE__,
|
||||
|
@ -18,16 +18,13 @@ use Symfony\Component\Messenger\Stamp\ValidationStamp;
|
||||
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
|
||||
use Symfony\Component\Serializer as SerializerComponent;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
class SerializerTest extends TestCase
|
||||
{
|
||||
public function testEncodedIsDecodable()
|
||||
{
|
||||
$serializer = new Serializer(
|
||||
new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()))
|
||||
);
|
||||
$serializer = new Serializer();
|
||||
|
||||
$envelope = new Envelope(new DummyMessage('Hello'));
|
||||
|
||||
@ -36,9 +33,7 @@ class SerializerTest extends TestCase
|
||||
|
||||
public function testEncodedWithStampsIsDecodable()
|
||||
{
|
||||
$serializer = new Serializer(
|
||||
new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()))
|
||||
);
|
||||
$serializer = new Serializer();
|
||||
|
||||
$envelope = (new Envelope(new DummyMessage('Hello')))
|
||||
->with(new SerializerStamp(array(ObjectNormalizer::GROUPS => array('foo'))))
|
||||
@ -50,9 +45,7 @@ class SerializerTest extends TestCase
|
||||
|
||||
public function testEncodedIsHavingTheBodyAndTypeHeader()
|
||||
{
|
||||
$serializer = new Serializer(
|
||||
new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()))
|
||||
);
|
||||
$serializer = new Serializer();
|
||||
|
||||
$encoded = $serializer->encode(new Envelope(new DummyMessage('Hello')));
|
||||
|
||||
@ -81,11 +74,7 @@ class SerializerTest extends TestCase
|
||||
|
||||
public function testEncodedWithSymfonySerializerForStamps()
|
||||
{
|
||||
$serializer = new Serializer(
|
||||
new SerializerComponent\Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder())),
|
||||
'json',
|
||||
array()
|
||||
);
|
||||
$serializer = new Serializer();
|
||||
|
||||
$envelope = (new Envelope(new DummyMessage('Hello')))
|
||||
->with($serializerStamp = new SerializerStamp(array(ObjectNormalizer::GROUPS => array('foo'))))
|
||||
@ -102,7 +91,7 @@ class SerializerTest extends TestCase
|
||||
|
||||
$decoded = $serializer->decode($encoded);
|
||||
|
||||
$this->assertEquals($serializerStamp, $decoded->get(SerializerStamp::class));
|
||||
$this->assertEquals($validationStamp, $decoded->get(ValidationStamp::class));
|
||||
$this->assertEquals($serializerStamp, $decoded->last(SerializerStamp::class));
|
||||
$this->assertEquals($validationStamp, $decoded->last(ValidationStamp::class));
|
||||
}
|
||||
}
|
||||
|
@ -42,10 +42,11 @@ class WorkerTest extends TestCase
|
||||
|
||||
public function testWorkerDoesNotWrapMessagesAlreadyWrappedWithReceivedMessage()
|
||||
{
|
||||
$envelope = (new Envelope(new DummyMessage('API')))->with(new ReceivedStamp());
|
||||
$envelope = new Envelope(new DummyMessage('API'));
|
||||
$receiver = new CallbackReceiver(function ($handler) use ($envelope) {
|
||||
$handler($envelope);
|
||||
});
|
||||
$envelope = $envelope->with(new ReceivedStamp());
|
||||
|
||||
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
|
||||
$bus->expects($this->at(0))->method('dispatch')->with($envelope)->willReturn($envelope);
|
||||
|
@ -17,6 +17,7 @@ use Symfony\Component\Messenger\Exception\LogicException;
|
||||
use Symfony\Component\Messenger\Stamp\SerializerStamp;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer as SymfonySerializer;
|
||||
use Symfony\Component\Serializer\SerializerInterface as SymfonySerializerInterface;
|
||||
@ -34,9 +35,9 @@ class Serializer implements SerializerInterface
|
||||
private $format;
|
||||
private $context;
|
||||
|
||||
public function __construct(SymfonySerializerInterface $serializer, string $format = 'json', array $context = array())
|
||||
public function __construct(SymfonySerializerInterface $serializer = null, string $format = 'json', array $context = array())
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
$this->serializer = $serializer ?? self::create()->serializer;
|
||||
$this->format = $format;
|
||||
$this->context = $context;
|
||||
}
|
||||
@ -48,7 +49,7 @@ class Serializer implements SerializerInterface
|
||||
}
|
||||
|
||||
$encoders = array(new XmlEncoder(), new JsonEncoder());
|
||||
$normalizers = array(new ObjectNormalizer());
|
||||
$normalizers = array(new ArrayDenormalizer(), new ObjectNormalizer());
|
||||
$serializer = new SymfonySerializer($normalizers, $encoders);
|
||||
|
||||
return new self($serializer);
|
||||
@ -70,9 +71,8 @@ class Serializer implements SerializerInterface
|
||||
$stamps = $this->decodeStamps($encodedEnvelope);
|
||||
|
||||
$context = $this->context;
|
||||
/** @var SerializerStamp|null $serializerStamp */
|
||||
if ($serializerStamp = $stamps[SerializerStamp::class] ?? null) {
|
||||
$context = $serializerStamp->getContext() + $context;
|
||||
if (isset($stamps[SerializerStamp::class])) {
|
||||
$context = end($stamps[SerializerStamp::class])->getContext() + $context;
|
||||
}
|
||||
|
||||
$message = $this->serializer->deserialize($encodedEnvelope['body'], $encodedEnvelope['headers']['type'], $this->format, $context);
|
||||
@ -87,7 +87,7 @@ class Serializer implements SerializerInterface
|
||||
{
|
||||
$context = $this->context;
|
||||
/** @var SerializerStamp|null $serializerStamp */
|
||||
if ($serializerStamp = $envelope->get(SerializerStamp::class)) {
|
||||
if ($serializerStamp = $envelope->last(SerializerStamp::class)) {
|
||||
$context = $serializerStamp->getContext() + $context;
|
||||
}
|
||||
|
||||
@ -107,7 +107,10 @@ class Serializer implements SerializerInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$stamps[] = $this->serializer->deserialize($value, substr($name, \strlen(self::STAMP_HEADER_PREFIX)), $this->format, $this->context);
|
||||
$stamps[] = $this->serializer->deserialize($value, substr($name, \strlen(self::STAMP_HEADER_PREFIX)).'[]', $this->format, $this->context);
|
||||
}
|
||||
if ($stamps) {
|
||||
$stamps = array_merge(...$stamps);
|
||||
}
|
||||
|
||||
return $stamps;
|
||||
@ -115,13 +118,13 @@ class Serializer implements SerializerInterface
|
||||
|
||||
private function encodeStamps(Envelope $envelope): array
|
||||
{
|
||||
if (!$stamps = $envelope->all()) {
|
||||
if (!$allStamps = $envelope->all()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
foreach ($stamps as $stamp) {
|
||||
$headers[self::STAMP_HEADER_PREFIX.\get_class($stamp)] = $this->serializer->serialize($stamp, $this->format, $this->context);
|
||||
foreach ($allStamps as $class => $stamps) {
|
||||
$headers[self::STAMP_HEADER_PREFIX.$class] = $this->serializer->serialize($stamps, $this->format, $this->context);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
|
Reference in New Issue
Block a user