-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Messenger][DX] Uses custom method names for handlers #27034
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
Conversation
03e2a89
to
f9c1e0d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MessageSubscriberInterface::getHandledMessages
docblock misses an update
/** | ||
* Used to transform an object and a method to a callable handler. | ||
* | ||
* @author Samuel Roze <samuel.roze@gmail.com> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@internal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, added 👍
$handlersByMessage = array(); | ||
|
||
foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { | ||
foreach ($tags as $tag) { | ||
$handles = isset($tag['handles']) ? array($tag['handles']) : $this->guessHandledClasses($r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()), $serviceId); | ||
if (isset($tag['handles'])) { | ||
$handles = isset($tag['method']) ? array($tag['handles'] => $tag['method']) : array($tag['handles']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it check the method exists to warn about typos at compile time?
$priority = $tag['priority'] ?? 0; | ||
|
||
foreach ($handles as $messageClass) { | ||
foreach ($handles as $messageClass => $method) { | ||
if (is_int($messageClass)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add a \
in front of is_*
calls to be consistent with the existing one below
public function __construct($object, string $method) | ||
{ | ||
if (!is_object($object)) { | ||
throw new \InvalidArgumentException(sprintf('Expected an object as argument but got %s', gettype($object))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
\is_object
, \gettype
(no, I'm not a bot :))
e6abce2
to
06f1eae
Compare
if ('__invoke' !== $method) { | ||
$wrapperDefinition = (new Definition(MethodOnObjectHandler::class))->setArguments(array(new Reference($serviceId), $method)); | ||
|
||
$definitions[$serviceId = hash('sha1', $messageClass.':'.$messagePriority.':'.$serviceId)] = $wrapperDefinition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it does not work for
public static function getHandledMessages(): array
{
return [
CreateNumber::class => 'method',
CreateNumber::class => 'anotherMethod',
];
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, please use ContainerBuilder::hash instead of sha1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tobion Your array does not work. It has a single item in it as PHP does not allow duplicate keys.
however, the hash should indeed include the method name, as you can have multiple methods.
and the final service id should not be only the hash. It should have a prefix before it to make it easier to identify services in error message (messenger.method_object_handler.<hash>
could make sense)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, method should be in the hash. Updated 👍
908fe56
to
99e8cb5
Compare
From my experience the EventSubscriber return value to configure callbacks has a really bad DX. It's not clear what array structure can be returned without reading the doc. Repeating that in the How about using a value object like public static function getHandledMessages(): array
{
return [
new MessageCallback(CreateNumber::class, 'createNumber', 10),
new MessageCallback(AnotherMessage::class, 'anotherMethod'),
];
} class MessageCallback
{
public function __construct(string $messageClass, string $method = '__invoke', int $priority = 0)
{
}
} This way the code is self-explaining and you don't have to deal with mixed returns like array of array or array of string or non-associative array. |
I like it! |
But the point of this PR is actually to make it consistent with the EventSubscriber to improve the developer experience 🙃 |
Learning from mistakes and improving them seems more important to me. Also the EventSubscriber with it's array structure is probably not gonna make it like this into the EventDispatcher PSR that got revived. But something like my propal is really flexible and clear. Just make MessageCallback an interface with getters and voila. public static function getHandledMessages(): iterable
{
return new PublicMethodsAsHandlers(self::class);
} |
public static function getHandledMessages(): HandledMessagesDescription but this looks like a lot of code infrastructure, which means this works only with a compiled container, because when not compiled the overhead of building this configuration can become significant, especially compared to the array approach, isn't it? |
I don't see where the overhead is when using a value object or a fluent api? This is just setters and getters. |
@nicolas-grekas I prefer the array approach (with the I like the |
@sroze the array of EventSubscriberInteface is very simple when you have to register a single listener. But when you need to register 2 listeners for the same event, I admit that I get it wrong half of the time (doing |
Would it make sense to use the static method as a configurator? static function configureHandledMessages(HandledMessagesDescriptor $config)
{
$config->...
} BUT on the other end, any objects we put here tightly couples the interface to the descriptor. |
One more idea that closes the circle in my eyes: Remove https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Messenger/Handler/MessageHandlerInterface.php and instead offer a trait/abstract class that implements public static function getHandledMessages(): iterable
{
return [
new ReflectedMethodCallback(self::class, '__invoke'),
];
} This is self-explaining, does not require more code than implementing the marker interface and does not require a marker interface at all. A marker interface without methods is usually just a workaround anyway. |
I think this is building infrastructure on top of conventions. We should design first and implement after IMHO. Disclaimer: the arguments I'm giving here are done while taking the existing static method descriptions we already have. @Tobion uses the word "mistake" above, I'm really not sure this was a mistake at all. And more importantly, I want to be sure whatever new system we use is actually better. |
As @Tobion spotted, using an injected argument enforces a single style of configuration (the DX is bound to the style provided by I'm also not really convinced by returning objects, for similar reasons: a locally created object forces also a style, or if we make it an interface, the interface could only provide a way to retrieve the state of the configuration, without providing any contractual guarantee (unlike the injected argument btw). For this reasons, I now think this method should return an array. |
@sroze if you want to move forward here, I suggest to isolate things that are unrelated to the return format of the static method in another PR if that makes sense. |
@nicolas-grekas i.e. revert to the array-notation, right? |
e23faea
to
a5a2a75
Compare
Alright, that's fair that there are different point of views and it's a tricky question, therefore let's make that in two steps: I reverted the introduction of the VO and kept the array EventDispatcher-like syntax (while changing the |
Shall we go ahead with this one? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense to me as is. Let's move forward 👍
@@ -23,6 +23,7 @@ | |||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface; | |||
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; | |||
use Symfony\Component\Messenger\TraceableMessageBus; | |||
use Symfony\Component\Messenger\Handler\MethodOnObjectHandler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alpha order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated 👍
if ('__invoke' !== $method) { | ||
$wrapperDefinition = (new Definition(MethodOnObjectHandler::class))->setArguments(array(new Reference($serviceId), $method)); | ||
|
||
$definitions[$serviceId = 'messenger.method_on_object_handler.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing dot at start of service is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But can't we pass array($def, $method) instead of using this handler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not the way it's done now because we simply wire the handlers (i.e. callables
). That way is also simpler :)
0ddb2d1
to
5fab1d7
Compare
} | ||
|
||
if ('__invoke' !== $method) { | ||
$wrapperDefinition = (new Definition(MethodOnObjectHandler::class))->setArguments(array(new Reference($serviceId), $method)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$wrapperDefinition = (new Definition('callable'))->addArgument(array(new Reference($serviceId), $method))->setFactory('Closure::fromCallable');
cheers :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:dance:
$messageClass = $method; | ||
$method = '__invoke'; | ||
} | ||
|
||
if (\is_array($messageClass)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be else if
here, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed with elseif
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fabpot actually can't because we are setting $messagePriority
as well.
edd0e4b
to
8927224
Compare
8927224
to
f711250
Compare
Status: Needs review |
@fabpot could you update your review please? |
f711250
to
5470fcf
Compare
5470fcf
to
2461e51
Compare
…(sroze) This PR was merged into the 4.1 branch. Discussion ---------- [Messenger][DX] Uses custom method names for handlers | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #26685 (comment) | License | MIT | Doc PR | ø This has been discussed mostly in the [`MessageHandlerInterface` pull-request](#26685). For consistency reasons and convenience, this PR adds the ability to configure the method to be used on handlers: ```php use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Component\Messenger\Handler\MessageSubscriberConfiguration; class CreateNumberMessageHandler implements MessageSubscriberInterface { /** * {@inheritdoc} */ public static function getHandledMessages(): array { return [ CreateNumber::class => ['createNumber', 10], AnotherMessage::class => 'anotherMethod', ]; } public function createNumber(CreateNumber $command) { // ... } } ``` Commits ------- 2461e51 [Messenger][DX] Uses custom method names for handlers
This has been discussed mostly in the
MessageHandlerInterface
pull-request. For consistency reasons and convenience, this PR adds the ability to configure the method to be used on handlers: