Skip to content

Commit

Permalink
feature #29159 [Messenger] collect all stamps added on Envelope as co…
Browse files Browse the repository at this point in the history
…llections (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
-------

2e98859 [Messenger] collect all stamps added on Envelope as collections
  • Loading branch information
fabpot committed Nov 12, 2018
2 parents e947043 + 2e98859 commit fb249f0
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 44 deletions.
2 changes: 2 additions & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Expand Up @@ -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
-----
Expand Down
16 changes: 10 additions & 6 deletions src/Symfony/Component/Messenger/Envelope.php
Expand Up @@ -36,7 +36,7 @@ public function __construct($message, StampInterface ...$stamps)
$this->message = $message;

foreach ($stamps as $stamp) {
$this->stamps[\get_class($stamp)] = $stamp;
$this->stamps[\get_class($stamp)][] = $stamp;
}
}

Expand All @@ -48,22 +48,26 @@ public function with(StampInterface ...$stamps): self
$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;
}

Expand Down
Expand Up @@ -35,7 +35,7 @@ public function __construct(SendersLocatorInterface $sendersLocator)
*/
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);
}
Expand Down
Expand Up @@ -38,7 +38,7 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
$message = $envelope->getMessage();
$groups = null;
/** @var ValidationStamp|null $validationStamp */
if ($validationStamp = $envelope->get(ValidationStamp::class)) {
if ($validationStamp = $envelope->last(ValidationStamp::class)) {
$groups = $validationStamp->getGroups();
}

Expand Down
12 changes: 6 additions & 6 deletions src/Symfony/Component/Messenger/Tests/EnvelopeTest.php
Expand Up @@ -29,7 +29,7 @@ public function testConstruct()

$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()
Expand All @@ -39,13 +39,13 @@ public function testWithReturnsNewInstance()
$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()
Expand All @@ -57,8 +57,8 @@ public function testAll()

$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]);
}
}
Expand Up @@ -56,7 +56,7 @@ public function testItTracesDispatchWithEnvelope()
$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__,
Expand Down
Expand Up @@ -18,16 +18,13 @@
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'));

Expand All @@ -36,9 +33,7 @@ public function testEncodedIsDecodable()

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'))))
Expand All @@ -50,9 +45,7 @@ public function testEncodedWithStampsIsDecodable()

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')));

Expand Down Expand Up @@ -81,11 +74,7 @@ public function testUsesTheCustomFormatAndContext()

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'))))
Expand All @@ -102,7 +91,7 @@ public function testEncodedWithSymfonySerializerForStamps()

$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));
}
}
3 changes: 2 additions & 1 deletion src/Symfony/Component/Messenger/Tests/WorkerTest.php
Expand Up @@ -42,10 +42,11 @@ public function testWorkerDispatchTheReceivedMessage()

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);
Expand Down
Expand Up @@ -17,6 +17,7 @@
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;
Expand All @@ -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;
}
Expand All @@ -48,7 +49,7 @@ public static function create(): self
}

$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);
Expand All @@ -70,9 +71,8 @@ public function decode(array $encodedEnvelope): Envelope
$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);
Expand All @@ -87,7 +87,7 @@ public function encode(Envelope $envelope): array
{
$context = $this->context;
/** @var SerializerStamp|null $serializerStamp */
if ($serializerStamp = $envelope->get(SerializerStamp::class)) {
if ($serializerStamp = $envelope->last(SerializerStamp::class)) {
$context = $serializerStamp->getContext() + $context;
}

Expand All @@ -107,21 +107,24 @@ private function decodeStamps(array $encodedEnvelope): array
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;
}

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;
Expand Down

0 comments on commit fb249f0

Please sign in to comment.