Skip to content

Commit

Permalink
[Messenger] Add handled & sent stamps
Browse files Browse the repository at this point in the history
  • Loading branch information
ogizanagi committed Nov 15, 2018
1 parent 8ce6f5e commit 2f5acf7
Show file tree
Hide file tree
Showing 16 changed files with 325 additions and 16 deletions.
Expand Up @@ -1578,9 +1578,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
}
$senders = array_map(function ($sender) use ($senderAliases) {
return new Reference($senderAliases[$sender] ?? $sender);
}, $messageConfiguration['senders']);
$senders = array();
foreach ($messageConfiguration['senders'] as $sender) {
$senders[$sender] = new Reference($senderAliases[$sender] ?? $sender);
}

$sendersId = 'messenger.senders.'.$message;
$container->register($sendersId, RewindableGenerator::class)
Expand Down
Expand Up @@ -569,7 +569,10 @@ public function testMessengerRouting()
);

$this->assertSame($messageToSendAndHandleMapping, $senderLocatorDefinition->getArgument(1));
$this->assertEquals(array(new Reference('messenger.transport.amqp'), new Reference('audit')), $container->getDefinition('messenger.senders.'.DummyMessage::class)->getArgument(0)[0]->getValues());
$this->assertEquals(array(
'amqp' => new Reference('messenger.transport.amqp'),
'audit' => new Reference('audit'),
), $container->getDefinition('messenger.senders.'.DummyMessage::class)->getArgument(0)[0]->getValues());
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
4.2.0
-----

* Added `HandledStamp` & `SentStamp` stamps
* All the changes below are BC BREAKS
* Senders and handlers subscribing to parent interfaces now receive *all* matching messages, wildcard included
* `MessageBusInterface::dispatch()`, `MiddlewareInterface::handle()` and `SenderInterface::send()` return `Envelope`
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Messenger/Handler/HandlersLocator.php
Expand Up @@ -40,9 +40,9 @@ public function getHandlers(Envelope $envelope): iterable
$seen = array();

foreach (self::listTypes($envelope) as $type) {
foreach ($this->handlers[$type] ?? array() as $handler) {
foreach ($this->handlers[$type] ?? array() as $alias => $handler) {
if (!\in_array($handler, $seen, true)) {
yield $seen[] = $handler;
yield $alias => $seen[] = $handler;
}
}
}
Expand Down
Expand Up @@ -25,7 +25,7 @@ interface HandlersLocatorInterface
/**
* Returns the handlers for the given message name.
*
* @return iterable|callable[]
* @return iterable|callable[] Indexed by handler alias if available
*/
public function getHandlers(Envelope $envelope): iterable;
}
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\NoHandlerForMessageException;
use Symfony\Component\Messenger\Handler\HandlersLocatorInterface;
use Symfony\Component\Messenger\Stamp\HandledStamp;

/**
* @author Samuel Roze <samuel.roze@gmail.com>
Expand All @@ -40,8 +41,8 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
$handler = null;
$message = $envelope->getMessage();
foreach ($this->handlersLocator->getHandlers($envelope) as $handler) {
$handler($message);
foreach ($this->handlersLocator->getHandlers($envelope) as $alias => $handler) {
$envelope = $envelope->with(HandledStamp::fromCallable($handler, $handler($message), \is_string($alias) ? $alias : null));
}
if (null === $handler && !$this->allowNoHandlers) {
throw new NoHandlerForMessageException(sprintf('No handler for message "%s".', \get_class($envelope->getMessage())));
Expand Down
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
use Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface;

/**
Expand Down Expand Up @@ -42,8 +43,8 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
$handle = false;
$sender = null;

foreach ($this->sendersLocator->getSenders($envelope, $handle) as $sender) {
$envelope = $sender->send($envelope);
foreach ($this->sendersLocator->getSenders($envelope, $handle) as $alias => $sender) {
$envelope = $sender->send($envelope)->with(new SentStamp(\get_class($sender), \is_string($alias) ? $alias : null));
}

if (null === $sender || $handle) {
Expand Down
89 changes: 89 additions & 0 deletions src/Symfony/Component/Messenger/Stamp/HandledStamp.php
@@ -0,0 +1,89 @@
<?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;

/**
* Stamp identifying a message handled by the `HandleMessageMiddleware` middleware
* and storing the handler returned value.
*
* @see \Symfony\Component\Messenger\Middleware\HandleMessageMiddleware
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @experimental in 4.2
*/
final class HandledStamp implements StampInterface
{
private $result;
private $callableName;
private $handlerAlias;

/**
* @param mixed $result The returned value of the message handler
*/
public function __construct($result, string $callableName, string $handlerAlias = null)
{
$this->result = $result;
$this->callableName = $callableName;
$this->handlerAlias = $handlerAlias;
}

/**
* @param mixed $result The returned value of the message handler
*/
public static function fromCallable(callable $handler, $result, string $handlerAlias = null): self
{
if (\is_array($handler)) {
if (\is_object($handler[0])) {
return new self($result, \get_class($handler[0]).'::'.$handler[1], $handlerAlias);
}

return new self($result, $handler[0].'::'.$handler[1], $handlerAlias);
}

if (\is_string($handler)) {
return new self($result, $handler, $handlerAlias);
}

if ($handler instanceof \Closure) {
$r = new \ReflectionFunction($handler);
if (false !== strpos($r->name, '{closure}')) {
return new self($result, 'Closure', $handlerAlias);
}
if ($class = $r->getClosureScopeClass()) {
return new self($result, $class->name.'::'.$r->name, $handlerAlias);
}

return new self($result, $r->name, $handlerAlias);
}

return new self($result, \get_class($handler).'::__invoke', $handlerAlias);
}

/**
* @return mixed
*/
public function getResult()
{
return $this->result;
}

public function getCallableName(): string
{
return $this->callableName;
}

public function getHandlerAlias(): ?string
{
return $this->handlerAlias;
}
}
43 changes: 43 additions & 0 deletions src/Symfony/Component/Messenger/Stamp/SentStamp.php
@@ -0,0 +1,43 @@
<?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;

/**
* Marker stamp identifying a message sent by the `SendMessageMiddleware`.
*
* @see \Symfony\Component\Messenger\Middleware\SendMessageMiddleware
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @experimental in 4.2
*/
final class SentStamp implements StampInterface
{
private $senderClass;
private $senderAlias;

public function __construct(string $senderClass, string $senderAlias = null)
{
$this->senderAlias = $senderAlias;
$this->senderClass = $senderClass;
}

public function getSenderClass(): string
{
return $this->senderClass;
}

public function getSenderAlias(): ?string
{
return $this->senderAlias;
}
}
@@ -0,0 +1,30 @@
<?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\Tests\Handler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;

class HandlersLocatorTest extends TestCase
{
public function testItYieldsProvidedAliasAsKey()
{
$handler = $this->createPartialMock(\stdClass::class, array('__invoke'));
$locator = new HandlersLocator(array(
DummyMessage::class => array('dummy' => $handler),
));

$this->assertSame(array('dummy' => $handler), iterator_to_array($locator->getHandlers(new Envelope(new DummyMessage('a')))));
}
}
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\Middleware\StackMiddleware;
use Symfony\Component\Messenger\Stamp\HandledStamp;
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;

Expand All @@ -36,6 +37,55 @@ public function testItCallsTheHandlerAndNextMiddleware()
$middleware->handle($envelope, $this->getStackMock());
}

/**
* @dataProvider itAddsHandledStampsProvider
*/
public function testItAddsHandledStamps(array $handlers, array $expectedStamps)
{
$message = new DummyMessage('Hey');
$envelope = new Envelope($message);

$middleware = new HandleMessageMiddleware(new HandlersLocator(array(
DummyMessage::class => $handlers,
)));

$envelope = $middleware->handle($envelope, $this->getStackMock());

$this->assertEquals($expectedStamps, $envelope->all(HandledStamp::class));
}

public function itAddsHandledStampsProvider()
{
$first = $this->createPartialMock(\stdClass::class, array('__invoke'));
$first->method('__invoke')->willReturn('first result');
$firstClass = \get_class($first);

$second = $this->createPartialMock(\stdClass::class, array('__invoke'));
$second->method('__invoke')->willReturn(null);
$secondClass = \get_class($second);

yield 'A stamp is added' => array(
array($first),
array(new HandledStamp('first result', $firstClass.'::__invoke')),
);

yield 'A stamp is added per handler' => array(
array($first, $second),
array(
new HandledStamp('first result', $firstClass.'::__invoke'),
new HandledStamp(null, $secondClass.'::__invoke'),
),
);

yield 'Yielded locator alias is used' => array(
array('first_alias' => $first, $second),
array(
new HandledStamp('first result', $firstClass.'::__invoke', 'first_alias'),
new HandledStamp(null, $secondClass.'::__invoke'),
),
);
}

/**
* @expectedException \Symfony\Component\Messenger\Exception\NoHandlerForMessageException
* @expectedExceptionMessage No handler for message "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"
Expand Down
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
use Symfony\Component\Messenger\Tests\Fixtures\ChildDummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
Expand All @@ -33,7 +34,12 @@ public function testItSendsTheMessageToAssignedSender()

$sender->expects($this->once())->method('send')->with($envelope)->willReturn($envelope);

$middleware->handle($envelope, $this->getStackMock(false));
$envelope = $middleware->handle($envelope, $this->getStackMock(false));

/* @var SentStamp $stamp */
$this->assertInstanceOf(SentStamp::class, $stamp = $envelope->last(SentStamp::class), 'it adds a sent stamp');
$this->assertNull($stamp->getSenderAlias());
$this->assertStringMatchesFormat('Mock_SenderInterface_%s', $stamp->getSenderClass());
}

public function testItSendsTheMessageToAssignedSenderWithPreWrappedMessage()
Expand Down Expand Up @@ -128,6 +134,8 @@ public function testItSkipsReceivedMessages()

$sender->expects($this->never())->method('send');

$middleware->handle($envelope, $this->getStackMock());
$envelope = $middleware->handle($envelope, $this->getStackMock());

$this->assertNull($envelope->last(SentStamp::class), 'it does not add sent stamp for received messages');
}
}

0 comments on commit 2f5acf7

Please sign in to comment.