From 34e7781c5c395f21108f2ba4c5e44cbae5aaad80 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 10 May 2019 12:16:07 -0400 Subject: [PATCH] Adding a new NonSendableStampInterface to avoid sending certain stamps Fixes a bug where Symfony serialization of the AmqpReceivedStamp sometimes caused problems. --- src/Symfony/Component/Messenger/CHANGELOG.md | 3 ++ src/Symfony/Component/Messenger/Envelope.php | 16 ++++++++ .../Stamp/NonSendableStampInterface.php | 23 +++++++++++ .../Messenger/Tests/EnvelopeTest.php | 38 +++++++++++++++++++ .../Serialization/PhpSerializerTest.php | 17 +++++++++ .../Serialization/SerializerTest.php | 16 ++++++++ .../Transport/AmqpExt/AmqpReceivedStamp.php | 4 +- .../Doctrine/DoctrineReceivedStamp.php | 4 +- .../Transport/RedisExt/RedisReceivedStamp.php | 4 +- .../Transport/Serialization/PhpSerializer.php | 3 ++ .../Transport/Serialization/Serializer.php | 3 ++ .../Serialization/SerializerInterface.php | 3 ++ 12 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 08a3520324..49d04feb1f 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -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` diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php index 0be3355e2e..825ebf79e4 100644 --- a/src/Symfony/Component/Messenger/Envelope.php +++ b/src/Symfony/Component/Messenger/Envelope.php @@ -80,6 +80,22 @@ final class Envelope 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; diff --git a/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php b/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php new file mode 100644 index 0000000000..ca8c31078e --- /dev/null +++ b/src/Symfony/Component/Messenger/Stamp/NonSendableStampInterface.php @@ -0,0 +1,23 @@ + + * + * 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 + * + * @experimental in 4.3 + */ +interface NonSendableStampInterface extends StampInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php index 417d3dedc0..eb99c0a3b0 100644 --- a/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php +++ b/src/Symfony/Component/Messenger/Tests/EnvelopeTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; 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; @@ -50,6 +51,30 @@ class EnvelopeTest extends TestCase $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'); @@ -92,3 +117,16 @@ class EnvelopeTest extends TestCase $this->assertCount(1, $envelope->all(ReceivedStamp::class)); } } + +class DummyExtendsDelayStamp extends DelayStamp +{ +} +interface DummyFooBarStampInterface extends StampInterface +{ +} +interface DummyNothingImplementsMeStampInterface extends StampInterface +{ +} +class DummyImplementsFooBarStampInterface implements DummyFooBarStampInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php index 6e508365e5..c146d2619d 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization; 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; @@ -63,4 +64,20 @@ class PhpSerializerTest extends TestCase '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 +{ } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php index 16e2c66a71..f43ad2efcb 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization; 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; @@ -193,4 +194,19 @@ class SerializerTest extends TestCase '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 +{ } diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php index 78ccb08add..65c0173907 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpReceivedStamp.php @@ -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; diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php index f11217a74a..536ecacd4c 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineReceivedStamp.php @@ -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 * * @experimental in 4.3 */ -class DoctrineReceivedStamp implements StampInterface +class DoctrineReceivedStamp implements NonSendableStampInterface { private $id; diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php index c0b6ad37bd..2f6b5c2484 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisReceivedStamp.php @@ -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 * * @experimental in 4.3 */ -class RedisReceivedStamp implements StampInterface +class RedisReceivedStamp implements NonSendableStampInterface { private $id; diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php index a916bf795c..b65c0fcf63 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/PhpSerializer.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * @author Ryan Weaver @@ -40,6 +41,8 @@ class PhpSerializer implements SerializerInterface */ public function encode(Envelope $envelope): array { + $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class); + $body = addslashes(serialize($envelope)); return [ diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 371e3ce14f..f009fb7551 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization; 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; @@ -98,6 +99,8 @@ class Serializer implements SerializerInterface $context = $serializerStamp->getContext() + $context; } + $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class); + $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope); return [ diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php index f534659f50..01a4abc16e 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/SerializerInterface.php @@ -39,6 +39,9 @@ interface SerializerInterface * 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) - a key/value pair of headers