Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Messenger] Add handled & sent stamps #29166

Merged
merged 1 commit into from Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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));
ogizanagi marked this conversation as resolved.
Show resolved Hide resolved
}

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
ogizanagi marked this conversation as resolved.
Show resolved Hide resolved
{
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()
ogizanagi marked this conversation as resolved.
Show resolved Hide resolved
{
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');
}
}