feature #31471 [Messenger] Add "non sendable" stamps (weaverryan)

This PR was merged into the 4.3 branch.

Discussion
----------

[Messenger] Add "non sendable" stamps

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | yes
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #31460
| License       | MIT
| Doc PR        | not needed

Fixes a bug where Symfony serialization of the AmqpReceivedStamp sometimes caused problems.

It's still a mystery why the `AmqpReceivedStamp` caused a segfault *sometimes* when going through the Symfony serializer or the `VarDumper`. But, that stamp really didn't need to be sent on redelivery anyways.

I don't love making the removal the responsibility of the serializers, but it didn't work well anywhere else.

Cheers!

Commits
-------

34e7781c5c Adding a new NonSendableStampInterface to avoid sending certain stamps
This commit is contained in:
Fabien Potencier 2019-05-21 06:57:28 +02:00
commit 65458059d8
12 changed files with 128 additions and 6 deletions

View File

@ -4,6 +4,9 @@ CHANGELOG
4.3.0 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: * [BC BREAK] `SendersLocatorInterface` has an additional method:
`getSenderByAlias()`. `getSenderByAlias()`.
* Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders` * Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders`

View File

@ -80,6 +80,22 @@ final class Envelope
return $cloned; 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 public function last(string $stampFqcn): ?StampInterface
{ {
return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null; return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;

View File

@ -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
{
}

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\ReceivedStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
@ -50,6 +51,30 @@ class EnvelopeTest extends TestCase
$this->assertCount(1, $envelope->all(DelayStamp::class)); $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() public function testLast()
{ {
$receivedStamp = new ReceivedStamp('transport'); $receivedStamp = new ReceivedStamp('transport');
@ -92,3 +117,16 @@ class EnvelopeTest extends TestCase
$this->assertCount(1, $envelope->all(ReceivedStamp::class)); $this->assertCount(1, $envelope->all(ReceivedStamp::class));
} }
} }
class DummyExtendsDelayStamp extends DelayStamp
{
}
interface DummyFooBarStampInterface extends StampInterface
{
}
interface DummyNothingImplementsMeStampInterface extends StampInterface
{
}
class DummyImplementsFooBarStampInterface implements DummyFooBarStampInterface
{
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
@ -63,4 +64,20 @@ class PhpSerializerTest extends TestCase
'body' => 'O:13:"ReceivedSt0mp":0:{}', '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
{
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
@ -193,4 +194,19 @@ class SerializerTest extends TestCase
'headers' => ['type' => 'NonExistentClass'], '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
{
} }

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt; 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. * Stamp applied when a message is received from Amqp.
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class AmqpReceivedStamp implements StampInterface class AmqpReceivedStamp implements NonSendableStampInterface
{ {
private $amqpEnvelope; private $amqpEnvelope;
private $queueName; private $queueName;

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\Doctrine; 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> * @author Vincent Touzet <vincent.touzet@gmail.com>
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class DoctrineReceivedStamp implements StampInterface class DoctrineReceivedStamp implements NonSendableStampInterface
{ {
private $id; private $id;

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\RedisExt; namespace Symfony\Component\Messenger\Transport\RedisExt;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* @author Alexander Schranz <alexander@sulu.io> * @author Alexander Schranz <alexander@sulu.io>
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class RedisReceivedStamp implements StampInterface class RedisReceivedStamp implements NonSendableStampInterface
{ {
private $id; private $id;

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* @author Ryan Weaver<ryan@symfonycasts.com> * @author Ryan Weaver<ryan@symfonycasts.com>
@ -40,6 +41,8 @@ class PhpSerializer implements SerializerInterface
*/ */
public function encode(Envelope $envelope): array public function encode(Envelope $envelope): array
{ {
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
$body = addslashes(serialize($envelope)); $body = addslashes(serialize($envelope));
return [ return [

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\LogicException;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder;
@ -98,6 +99,8 @@ class Serializer implements SerializerInterface
$context = $serializerStamp->getContext() + $context; $context = $serializerStamp->getContext() + $context;
} }
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
$headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope); $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope);
return [ return [

View File

@ -39,6 +39,9 @@ interface SerializerInterface
* Encodes an envelope content (message & stamps) to a common format understandable by transports. * Encodes an envelope content (message & stamps) to a common format understandable by transports.
* The encoded array should only contain scalars and arrays. * 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: * The most common keys of the encoded array are:
* - `body` (string) - the message body * - `body` (string) - the message body
* - `headers` (string<string>) - a key/value pair of headers * - `headers` (string<string>) - a key/value pair of headers