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
-----
* 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`

View File

@ -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;

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

View File

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

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<ryan@symfonycasts.com>
@ -40,6 +41,8 @@ class PhpSerializer implements SerializerInterface
*/
public function encode(Envelope $envelope): array
{
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
$body = addslashes(serialize($envelope));
return [

View File

@ -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 [

View File

@ -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<string>) - a key/value pair of headers