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] make senders and handlers subscribing to parent interfaces receive *all* matching messages, wildcard included #29010

Merged
merged 1 commit into from
Oct 31, 2018

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Oct 28, 2018

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

Embeds #29006 for now.

From the CHANGELOG:

  • senders and handlers subscribing to parent interfaces now receive all matching messages, wildcard included
  • HandlerLocatorInterface::resolve() has been removed, use HandlersLocator::getHandlers() instead
  • SenderLocatorInterface::getSenderForMessage() has been removed, use SendersLocator::getSenders() instead
  • The ChainHandler and ChainSender classes have been removed
  • The ContainerHandlerLocator, AbstractHandlerLocator, SenderLocator and AbstractSenderLocator classes have been removed

The new HandlersLocatorInterface and SendersLocatorInterface interfaces return iterable of corresponding handlers/senders. This allows simplifying a lot the DI configuration and standalone usage.

Inheritance-based configuration is now stable: when a sender or a handler is bound to SomeMessageInterface, it will always get all messages of that kind. This fixes the unstable nature of the previous logic, where only the first matching type bound to a message would match, making routing/handling unpredictable (note that the new behavior is also how Laravel does it.)

Some cleanups found meanwhile:

  • the messenger.sender tag was useless, it's removed
  • the reponsibility of the "send-and-handle" decision has been moved to the locator, where it belongs
  • thanks to type+argument autowiring aliases, we don't need to force defining a default bus - gaining nice errors when a named argument has a typo
  • some services have been renamed to make them more conventional

As far as priorities are concerned, the priority number applies in the scope of the type bound to it: senders/handlers that are bound to more specific types are always called before less specific ones, no matter the defined priority.

@nicolas-grekas
Copy link
Member Author

@topbin thanks, fixed

Copy link
Contributor

@ro0NL ro0NL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks to type+argument autowiring aliases, we don't need to force defining a default bus

so potentially MessageBusInterface has no alias? currently the "message_bus" is still forced to exists few line down low.

@nicolas-grekas
Copy link
Member Author

currently the "message_bus" is still forced to exists few line down low

fixed

@ro0NL
Copy link
Contributor

ro0NL commented Oct 29, 2018

MessengerPass wont run if there's no message_bus service, its' not needed anymore IIUC (fixes #28959 i guess :))

@nicolas-grekas
Copy link
Member Author

MessengerPass wont run if there's no message_bus service

fixed - and also fixed the message in ControllerTrait.

@weaverryan
Copy link
Member

This is the approach I was absolutely hoping for. It's predicatable, but type-based. Awesome!

@skalpa
Copy link
Contributor

skalpa commented Oct 29, 2018

Wouldn't it be worth removing the handlers' priority altogether ? As it is now it's pretty confusing as it only sorts handlers that subscribe to the same type.

It made sense when the handler with the highest priority used to win, but a lot less now. IMHO, users should not expect handlers to be executed in a predictable order. If one needs BarHandler to be executed after FooHandler, then they shouldn't both subscribe to the same message type: FooHandler should dispatch an additional message that BarHandler will subscribe to.

@nicolas-grekas
Copy link
Member Author

If a subscriber can declare its priority, it can also declare a more specific type to register. So that's not a downgrade in terms of possibilities. Then, within one type, it can be useful to have priorities (otherwise why would we have added them?) so I think it's OK as is.

@skalpa
Copy link
Contributor

skalpa commented Oct 29, 2018

Honestly, I wouldn't know why priorities were added to start with ;-). Anyway, I'm fine with that, and this behavior can always be documented.
Btw, all your latest PRs on this component have been brilliant, thanks a lot.

@Tobion
Copy link
Contributor

Tobion commented Oct 29, 2018

IMHO, users should not expect handlers to be executed in a predictable order. If one needs BarHandler to be executed after FooHandler, then they shouldn't both subscribe to the same message type

Fully agree with @skalpa . You can also make it one handler that does the right thing internally. Also since priortiy now has a different meaning than before, I would suggest to remove it.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Oct 29, 2018

For message buses, priorities can be useless, but the component should also work for query buses. In this case, priorities could be important to have. I expect people to use flat message types most of the time also.

@nicolas-grekas
Copy link
Member Author

PR rebased, ready to merge.

}

if (($definition = $container->findDefinition($messengerMiddlewareId))->isAbstract()) {
$childDefinition = new ChildDefinition($messengerMiddlewareId);
$count = \count($definition->getArguments());
foreach (array_values($arguments ?? array()) as $key => $argument) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this logic should be removed: ChildDefinition already have their merging logic. Overwriting an existing parent argument is done by using index_0 as a key. Let's make things simpler by reusing existing knowledge.

));

$this->assertSame(array($sender), iterator_to_array($locator->getSenders(DummyMessage::class)));
$this->assertSame(array(), iterator_to_array($locator->getSenders(SecondMessage::class)));
Copy link
Contributor

@Tobion Tobion Oct 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's technically not safe to call iterator_to_array on the result as it's return type is iterable which can be an array. I usually have a helper function for cases like this iterableToArray

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a test case: we do expect an iterator. Even if it's implicit, at least it protects against changing the return value to an array.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then that should be a new assertion. A static code analyzer would complain about this code.

$messagePriority = $messageClass[1];
$messageClass = $messageClass[0];
if (\is_array($message)) {
list($message, $priority) = $message;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this case is not documented and seems to be a relict of the old subscriber return values. considering all the bc breaks, this could be removed as well I guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest doing so in another PR - here it's unrelated (and I feel like this compiler pass needs to be improved anyway)

return array($class => $class, '*' => '*');
}

return array($class => $class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array key does not seem to be used or useful. So you could put an array_values around the whole array union. This would clarify the code to me as it makes clear the array key is only relevant for the array union operation.

Copy link
Contributor

@Tobion Tobion Oct 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return array_values(array($class => $class) + ...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An array_values() call would add memory and CPU overhead for no reason. Yes, we don't use the keys. That's not an issue to me - arrays always have keys anyway. And actually having the key be the values makes it impossible to return twice the same value, which is part of the contract of this function. I'd keep as is.

@@ -6,38 +6,34 @@ CHANGELOG

* The component is not experimental anymore
* All the changes below are BC BREAKS
* senders and handlers subscribing to parent interfaces now receive *all* matching messages, wildcard included
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An upper case would be lovely here.

Copy link
Contributor

@sroze sroze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic. Let's defo make that in 4.2.

…s receive *all* matching messages, wildcard included
@sroze
Copy link
Contributor

sroze commented Oct 31, 2018

It took time, but here we go, this is in now. Thank you very much @nicolas-grekas.

@sroze sroze merged commit 1e7af4d into symfony:master Oct 31, 2018
sroze added a commit that referenced this pull request Oct 31, 2018
…arent interfaces receive *all* matching messages, wildcard included (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

 [Messenger] make senders and handlers subscribing to parent interfaces receive *all* matching messages, wildcard included

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

~Embeds #29006 for now.~

From the CHANGELOG:
 * senders and handlers subscribing to parent interfaces now receive *all* matching messages, wildcard included
 * `HandlerLocatorInterface::resolve()` has been removed, use `HandlersLocator::getHandlers()` instead
 * `SenderLocatorInterface::getSenderForMessage()` has been removed, use `SendersLocator::getSenders()` instead
 * The `ChainHandler` and `ChainSender` classes have been removed
 * The `ContainerHandlerLocator`, `AbstractHandlerLocator`, `SenderLocator` and `AbstractSenderLocator` classes have been removed

The new `HandlersLocatorInterface` and `SendersLocatorInterface` interfaces return **`iterable`** of corresponding handlers/senders. This allows simplifying a lot the DI configuration and standalone usage.

Inheritance-based configuration is now stable: when a sender or a handler is bound to `SomeMessageInterface`, it will always get all messages of that kind. This fixes the unstable nature of the previous logic, where only the first matching type bound to a message would match, making routing/handling unpredictable (note that the new behavior is also how Laravel does it.)

Some cleanups found meanwhile:
 * the `messenger.sender` tag was useless, it's removed
 * the reponsibility of the "send-and-handle" decision has been moved to the locator, where it belongs
 * thanks to type+argument autowiring aliases, we don't need to force defining a default bus - gaining nice errors when a named argument has a typo
 * some services have been renamed to make them more conventional

As far as priorities are concerned, the priority number applies in the scope of the type bound to it: senders/handlers that are bound to more specific types are always called before less specific ones, no matter the defined priority.

Commits
-------

1e7af4d [Messenger] make senders and handlers subscribing to parent interfaces receive *all* matching messages, wildcard included
@nicolas-grekas nicolas-grekas deleted the messenger-no-chain branch October 31, 2018 08:05
@nicolas-grekas
Copy link
Member Author

Awesome, thank you all for your help!

@bendavies
Copy link
Contributor

lots of cool changes to messenger - please don't forget about docs!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants