Skip to content

Commit

Permalink
Adding a new NonSendableStampInterface to avoid sending certain stamps
Browse files Browse the repository at this point in the history
Fixes a bug where Symfony serialization of the AmqpReceivedStamp sometimes caused problems.
  • Loading branch information
weaverryan committed May 13, 2019
1 parent 4559a65 commit 34e7781
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 6 deletions.
3 changes: 3 additions & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Expand Up @@ -4,6 +4,9 @@ CHANGELOG
4.3.0
-----

* Added `NonSendableStampInterface` that a stamp can implement if
it should not be sent to a transport. Transport serializers
must now check for these stamps and not encode them.
* [BC BREAK] `SendersLocatorInterface` has an additional method:
`getSenderByAlias()`.
* Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders`
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/Messenger/Envelope.php
Expand Up @@ -80,6 +80,22 @@ public function withoutAll(string $stampFqcn): self
return $cloned;
}

/**
* Removes all stamps that implement the given type.
*/
public function withoutStampsOfType(string $type): self
{
$cloned = clone $this;

foreach ($cloned->stamps as $class => $stamps) {
if ($class === $type || \is_subclass_of($class, $type)) {
unset($cloned->stamps[$class]);
}
}

return $cloned;
}

public function last(string $stampFqcn): ?StampInterface
{
return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;
Expand Down
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Stamp;

/**
* A stamp that should not be included with the Envelope if sent to a transport.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*
* @experimental in 4.3
*/
interface NonSendableStampInterface extends StampInterface
{
}
38 changes: 38 additions & 0 deletions src/Symfony/Component/Messenger/Tests/EnvelopeTest.php
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;

Expand Down Expand Up @@ -50,6 +51,30 @@ public function testWithoutAll()
$this->assertCount(1, $envelope->all(DelayStamp::class));
}

public function testWithoutStampsOfType()
{
$envelope = new Envelope(new DummyMessage('dummy'), [
new ReceivedStamp('transport1'),
new DelayStamp(5000),
new DummyExtendsDelayStamp(5000),
new DummyImplementsFooBarStampInterface(),
]);

$envelope2 = $envelope->withoutStampsOfType(DummyNothingImplementsMeStampInterface::class);
$this->assertEquals($envelope, $envelope2);

$envelope3 = $envelope2->withoutStampsOfType(ReceivedStamp::class);
$this->assertEmpty($envelope3->all(ReceivedStamp::class));

$envelope4 = $envelope3->withoutStampsOfType(DelayStamp::class);
$this->assertEmpty($envelope4->all(DelayStamp::class));
$this->assertEmpty($envelope4->all(DummyExtendsDelayStamp::class));

$envelope5 = $envelope4->withoutStampsOfType(DummyFooBarStampInterface::class);
$this->assertEmpty($envelope5->all(DummyImplementsFooBarStampInterface::class));
$this->assertEmpty($envelope5->all());
}

public function testLast()
{
$receivedStamp = new ReceivedStamp('transport');
Expand Down Expand Up @@ -92,3 +117,16 @@ public function testWrapWithEnvelope()
$this->assertCount(1, $envelope->all(ReceivedStamp::class));
}
}

class DummyExtendsDelayStamp extends DelayStamp
{
}
interface DummyFooBarStampInterface extends StampInterface
{
}
interface DummyNothingImplementsMeStampInterface extends StampInterface
{
}
class DummyImplementsFooBarStampInterface implements DummyFooBarStampInterface
{
}
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;

Expand Down Expand Up @@ -63,4 +64,20 @@ public function testDecodingFailsWithBadClass()
'body' => 'O:13:"ReceivedSt0mp":0:{}',
]);
}

public function testEncodedSkipsNonEncodeableStamps()
{
$serializer = new PhpSerializer();

$envelope = new Envelope(new DummyMessage('Hello'), [
new DummyPhpSerializerNonSendableStamp(),
]);

$encoded = $serializer->encode($envelope);
$this->assertNotContains('DummyPhpSerializerNonSendableStamp', $encoded['body']);
}
}

class DummyPhpSerializerNonSendableStamp implements NonSendableStampInterface
{
}
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
Expand Down Expand Up @@ -193,4 +194,19 @@ public function testDecodingFailsWithBadClass()
'headers' => ['type' => 'NonExistentClass'],
]);
}

public function testEncodedSkipsNonEncodeableStamps()
{
$serializer = new Serializer();

$envelope = new Envelope(new DummyMessage('Hello'), [
new DummySymfonySerializerNonSendableStamp(),
]);

$encoded = $serializer->encode($envelope);
$this->assertNotContains('DummySymfonySerializerNonSendableStamp', print_r($encoded['headers'], true));
}
}
class DummySymfonySerializerNonSendableStamp implements NonSendableStampInterface
{
}
Expand Up @@ -11,14 +11,14 @@

namespace Symfony\Component\Messenger\Transport\AmqpExt;

use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;

/**
* Stamp applied when a message is received from Amqp.
*
* @experimental in 4.3
*/
class AmqpReceivedStamp implements StampInterface
class AmqpReceivedStamp implements NonSendableStampInterface
{
private $amqpEnvelope;
private $queueName;
Expand Down
Expand Up @@ -11,14 +11,14 @@

namespace Symfony\Component\Messenger\Transport\Doctrine;

use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;

/**
* @author Vincent Touzet <vincent.touzet@gmail.com>
*
* @experimental in 4.3
*/
class DoctrineReceivedStamp implements StampInterface
class DoctrineReceivedStamp implements NonSendableStampInterface
{
private $id;

Expand Down
Expand Up @@ -11,14 +11,14 @@

namespace Symfony\Component\Messenger\Transport\RedisExt;

use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;

/**
* @author Alexander Schranz <alexander@sulu.io>
*
* @experimental in 4.3
*/
class RedisReceivedStamp implements StampInterface
class RedisReceivedStamp implements NonSendableStampInterface
{
private $id;

Expand Down
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;

/**
* @author Ryan Weaver<ryan@symfonycasts.com>
Expand Down Expand Up @@ -40,6 +41,8 @@ public function decode(array $encodedEnvelope): Envelope
*/
public function encode(Envelope $envelope): array
{
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);

$body = addslashes(serialize($envelope));

return [
Expand Down
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\LogicException;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
Expand Down Expand Up @@ -98,6 +99,8 @@ public function encode(Envelope $envelope): array
$context = $serializerStamp->getContext() + $context;
}

$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);

$headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope);

return [
Expand Down
Expand Up @@ -39,6 +39,9 @@ public function decode(array $encodedEnvelope): Envelope;
* Encodes an envelope content (message & stamps) to a common format understandable by transports.
* The encoded array should only contain scalars and arrays.
*
* Stamps that implement NonSendableStampInterface should
* not be encoded.
*
* The most common keys of the encoded array are:
* - `body` (string) - the message body
* - `headers` (string<string>) - a key/value pair of headers
Expand Down

0 comments on commit 34e7781

Please sign in to comment.