Skip to content

[Messenger] Add option allow_no_senders to enable throwing when a message doesn't have a sender #46053

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

Merged
merged 1 commit into from
Oct 20, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ CHANGELOG
* Remove `@internal` tag from secret vaults to allow them to be used directly outside the framework bundle and custom vaults to be added
* Deprecate `framework.form.legacy_error_messages` config node
* Add a `framework.router.cache_dir` configuration option to configure the default `Router` `cache_dir` option
* Add option `framework.messenger.buses.*.default_middleware.allow_no_senders` to enable throwing when a message doesn't have a sender

6.1
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1521,15 +1521,36 @@ function ($a) {
->end()
->scalarNode('default_bus')->defaultNull()->end()
->arrayNode('buses')
->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]])
->defaultValue(['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]])
->normalizeKeys(false)
->useAttributeAsKey('name')
->arrayPrototype()
->addDefaultsIfNotSet()
->children()
->enumNode('default_middleware')
->values([true, false, 'allow_no_handlers'])
->defaultTrue()
->arrayNode('default_middleware')
->beforeNormalization()
->ifTrue(function ($defaultMiddleware) { return \is_string($defaultMiddleware) || \is_bool($defaultMiddleware); })
->then(function ($defaultMiddleware): array {
if (\is_string($defaultMiddleware) && 'allow_no_handlers' === $defaultMiddleware) {
return [
'enabled' => true,
'allow_no_handlers' => true,
'allow_no_senders' => true,
];
}

return [
'enabled' => $defaultMiddleware,
'allow_no_handlers' => false,
'allow_no_senders' => true,
];
})
->end()
->canBeDisabled()
->children()
->booleanNode('allow_no_handlers')->defaultFalse()->end()
->booleanNode('allow_no_senders')->defaultTrue()->end()
->end()
->end()
->arrayNode('middleware')
->performNoDeepMerging()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2035,12 +2035,9 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
foreach ($config['buses'] as $busId => $bus) {
$middleware = $bus['middleware'];

if ($bus['default_middleware']) {
if ('allow_no_handlers' === $bus['default_middleware']) {
$defaultMiddleware['after'][1]['arguments'] = [true];
} else {
unset($defaultMiddleware['after'][1]['arguments']);
}
if ($bus['default_middleware']['enabled']) {
$defaultMiddleware['after'][0]['arguments'] = [$bus['default_middleware']['allow_no_senders']];
$defaultMiddleware['after'][1]['arguments'] = [$bus['default_middleware']['allow_no_handlers']];

// argument to add_bus_name_stamp_middleware
$defaultMiddleware['before'][0]['arguments'] = [$busId];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
abstract_arg('senders service locator'),
])
->set('messenger.middleware.send_message', SendMessageMiddleware::class)
->abstract()
->args([
service('messenger.senders_locator'),
service('event_dispatcher'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@
</xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="default_middleware">
<xsd:simpleType name="default_middleware_attr">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true" />
<xsd:enumeration value="false" />
Expand All @@ -436,6 +436,12 @@
</xsd:restriction>
</xsd:simpleType>

<xsd:complexType name="default_middleware_elem">
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="allow_no_handlers" type="xsd:boolean" />
<xsd:attribute name="allow_no_senders" type="xsd:boolean" />
</xsd:complexType>

<xsd:simpleType name="cookie_secure">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true" />
Expand Down Expand Up @@ -567,10 +573,11 @@

<xsd:complexType name="messenger_bus">
<xsd:sequence>
<xsd:element name="default-middleware" type="default_middleware_elem" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="middleware" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="default-middleware" type="default_middleware"/>
<xsd:attribute name="default-middleware" type="default_middleware_attr"/>
</xsd:complexType>

<xsd:complexType name="messenger_middleware">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,19 +383,19 @@ public function testBusMiddlewareDontMerge()
$this->assertEquals(
[
'existing_bus' => [
'default_middleware' => true,
'default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true],
'middleware' => [
['id' => 'existing_bus.middleware', 'arguments' => []],
],
],
'common_bus' => [
'default_middleware' => false,
'default_middleware' => ['enabled' => false, 'allow_no_handlers' => false, 'allow_no_senders' => true],
'middleware' => [
['id' => 'common_bus.new_middleware', 'arguments' => []],
],
],
'new_bus' => [
'default_middleware' => true,
'default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true],
'middleware' => [
['id' => 'new_bus.middleware', 'arguments' => []],
],
Expand Down Expand Up @@ -607,7 +607,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
],
],
'default_bus' => null,
'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]],
'buses' => ['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]],
'reset_on_message' => true,
],
'disallow_search_engine_index' => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,8 @@ public function testMessengerWithMultipleBuses()
['id' => 'reject_redelivered_message_middleware'],
['id' => 'dispatch_after_current_bus'],
['id' => 'failed_message_processing_middleware'],
['id' => 'send_message'],
['id' => 'handle_message'],
['id' => 'send_message', 'arguments' => [true]],
['id' => 'handle_message', 'arguments' => [false]],
], $container->getParameter('messenger.bus.commands.middleware'));
$this->assertTrue($container->has('messenger.bus.events'));
$this->assertSame([], $container->getDefinition('messenger.bus.events')->getArgument(0));
Expand All @@ -1024,8 +1024,8 @@ public function testMessengerWithMultipleBuses()
['id' => 'dispatch_after_current_bus'],
['id' => 'failed_message_processing_middleware'],
['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]],
['id' => 'send_message'],
['id' => 'handle_message'],
['id' => 'send_message', 'arguments' => [true]],
['id' => 'handle_message', 'arguments' => [false]],
], $container->getParameter('messenger.bus.events.middleware'));
$this->assertTrue($container->has('messenger.bus.queries'));
$this->assertSame([], $container->getDefinition('messenger.bus.queries')->getArgument(0));
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Messenger/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* Add `TransportNamesStamp` to change the transport while dispatching a message
* Add support for rate limited transports by using the RateLimiter component.
* Deprecate `MessageHandlerInterface` and `MessageSubscriberInterface`, use `#[AsMessageHandler]` instead
* Add new parameter `allowNoSenders` to `SendMessageMiddleware` to enable throwing when a message doesn't have a sender

6.1
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?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\Exception;

/**
* @author Jérémy Reynaud <jeremy@reynaud.io>
*/
class NoSenderForMessageException extends LogicException
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Psr\Log\NullLogger;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
use Symfony\Component\Messenger\Exception\NoSenderForMessageException;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
use Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface;
Expand All @@ -30,11 +31,13 @@ class SendMessageMiddleware implements MiddlewareInterface

private SendersLocatorInterface $sendersLocator;
private ?EventDispatcherInterface $eventDispatcher;
private bool $allowNoSenders;

public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null)
public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null, bool $allowNoSenders = true)
{
$this->sendersLocator = $sendersLocator;
$this->eventDispatcher = $eventDispatcher;
$this->allowNoSenders = $allowNoSenders;
$this->logger = new NullLogger();
}

Expand Down Expand Up @@ -64,6 +67,10 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
$this->logger->info('Sending message {class} with {alias} sender using {sender}', $context + ['alias' => $alias, 'sender' => $sender::class]);
$envelope = $sender->send($envelope->with(new SentStamp($sender::class, \is_string($alias) ? $alias : null)));
}

if (!$this->allowNoSenders && !$sender) {
throw new NoSenderForMessageException(sprintf('No sender for message "%s".', $context['class']));
}
}

if (null === $sender) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;
use Symfony\Component\Messenger\Exception\NoSenderForMessageException;
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
Expand Down Expand Up @@ -197,6 +198,40 @@ public function testItDoesNotDispatchWithNoSenders()
$middleware->handle($envelope, $this->getStackMock());
}

public function testThrowsNoRoutingException()
{
$envelope = new Envelope(new DummyMessage('original envelope'));

$dispatcher = $this->createMock(EventDispatcherInterface::class);

$sendersLocator = $this->createSendersLocator([DummyMessage::class => []], []);

$this->expectException(NoSenderForMessageException::class);
$this->expectExceptionMessage('No sender for message "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"');

$middleware = new SendMessageMiddleware($sendersLocator, $dispatcher, false);
$middleware->handle($envelope, $this->getStackMock(false));
}

public function testAllowNoRouting()
{
$envelope = new Envelope(new DummyMessage('original envelope'));

$sender = $this->createMock(SenderInterface::class);

$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher->expects($this->once())
->method('dispatch')
->with(new SendMessageToTransportsEvent($envelope, ['foo' => $sender]));

$sendersLocator = $this->createSendersLocator([DummyMessage::class => ['foo']], ['foo' => $sender]);
$middleware = new SendMessageMiddleware($sendersLocator, $dispatcher);

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

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

private function createSendersLocator(array $sendersMap, array $senders): SendersLocator
{
$container = $this->createMock(ContainerInterface::class);
Expand Down