feature #30650 Dispatching two events when a message is sent & handled (weaverryan)

This PR was merged into the 4.3-dev branch.

Discussion
----------

Dispatching two events when a message is sent & handled

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none
| License       | MIT
| Doc PR        | TODO

Alternative to #30646. This uses a more generic system, so you could do anything when a message is sent. The main use-case is when a message is dispatched by a 3rd party.

I didn't try to add *exhaustive* events everywhere: I added an event for a very specific use-case:

When a message is dispatched by a 3rd party, being able to add stamps (e.g. `DelayStamp` or a future `AmqpRoutingKeyStamp` before the message is sent. Example:

```php
class MailerMessageSendToTransportEventSubscriber implements EventSubscriberInterface
{
    public function onSendMessage(SendMessageToTransportsEvent $event)
    {
        $envelope = $event->getEnvelope();
        if (!$envelope->getMessage() instanceof SomeMailerMessage) {
            return;
        }

        $event->setEnvelope($envelope->with(new AmpqRoutingKeyStamp('mailer-route')));
    }

    public static function getSubscribedEvents()
    {
        return [SendMessageToTransportsEvent::class => 'onSendMessage'];
    }
}
```

Along with #30557, we will now have the following events, regarding async messages:
* Event when a message is sent to transports (this PR)
* Event when a message is received from transport, but before handling it
* Event when a message is received from transport and after handling it

Commits
-------

a7ad1b4ccc Dispatching two events when a message is sent & handled
This commit is contained in:
Fabien Potencier 2019-03-23 15:32:21 +01:00
commit e3970f9879
3 changed files with 80 additions and 2 deletions

View File

@ -0,0 +1,44 @@
<?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\Event;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Messenger\Envelope;
/**
* Event is dispatched before a message is sent to the transport.
*
* The event is *only* dispatched if the message will actually
* be sent to at least one transport. If the message is sent
* to multiple transports, the message is dispatched only one time.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
class SendMessageToTransportsEvent extends Event
{
private $envelope;
public function __construct(Envelope $envelope)
{
$this->envelope = $envelope;
}
public function getEnvelope(): Envelope
{
return $this->envelope;
}
public function setEnvelope(Envelope $envelope)
{
$this->envelope = $envelope;
}
}

View File

@ -13,7 +13,9 @@ namespace Symfony\Component\Messenger\Middleware;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
@ -30,10 +32,12 @@ class SendMessageMiddleware implements MiddlewareInterface
use LoggerAwareTrait;
private $sendersLocator;
private $eventDispatcher;
public function __construct(SendersLocatorInterface $sendersLocator)
public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null)
{
$this->sendersLocator = $sendersLocator;
$this->eventDispatcher = $eventDispatcher;
$this->logger = new NullLogger();
}
@ -58,7 +62,15 @@ class SendMessageMiddleware implements MiddlewareInterface
/** @var RedeliveryStamp|null $redeliveryStamp */
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
foreach ($this->sendersLocator->getSenders($envelope, $handle) as $alias => $sender) {
$senders = \iterator_to_array($this->sendersLocator->getSenders($envelope, $handle));
if (null !== $this->eventDispatcher && \count($senders) > 0) {
$event = new SendMessageToTransportsEvent($envelope);
$this->eventDispatcher->dispatch($event);
$envelope = $event->getEnvelope();
}
foreach ($senders as $alias => $sender) {
// on redelivery, only deliver to the given sender
if (null !== $redeliveryStamp && !$redeliveryStamp->shouldRedeliverToSender(\get_class($sender), $alias)) {
continue;

View File

@ -11,7 +11,9 @@
namespace Symfony\Component\Messenger\Tests\Middleware;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
@ -202,4 +204,24 @@ class SendMessageMiddlewareTest extends MiddlewareTestCase
$this->assertNull($envelope->last(SentStamp::class), 'it does not add sent stamp for received messages');
}
public function testItDispatchesTheEventOnceTime()
{
$envelope = new Envelope(new DummyMessage('original envelope'));
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher->expects($this->once())
->method('dispatch')
->with(new SendMessageToTransportsEvent($envelope));
$sender1 = $this->getMockBuilder(SenderInterface::class)->getMock();
$sender2 = $this->getMockBuilder(SenderInterface::class)->getMock();
$middleware = new SendMessageMiddleware(new SendersLocator([DummyMessage::class => [$sender1, $sender2]]), $dispatcher);
$sender1->expects($this->once())->method('send')->willReturn($envelope);
$sender2->expects($this->once())->method('send')->willReturn($envelope);
$middleware->handle($envelope, $this->getStackMock(false));
}
}