-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Messenger] Make HandleMessageMiddleware::callHandler
protected
#50980
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
base: 7.4
Are you sure you want to change the base?
Conversation
I made the following changes: - make `callHandler` protected - pass the HandlerDescriptor (can be used to read options from the handler) - pass the Envelope (can be used to read more stamps) This allows people to decorate the `HandleMessageMiddleware` and intercept the moment the handler is called. I currently have the following use cases for this in my application: 1) Every handler can decide if they want to wrap their handling in a transaction. This is done by a custom `#[WrapInTransaction]` attribute. When the attribute is there, we wrap it in a database transaction. I know the `DoctrineTransactionMiddleware` exists in the Symfony Doctrine bridge, but that runs as middleware and cannot be toggled individually. It would be possible to modify it so that it only runs when the handler has it enabled using an option, but that only works for buses with only 1 handler. For an event bus, with multiple handlers, that won't be a solution. 2) Every handler can decide if they want to ignore certain exceptions that are thrown. This is done by a custom `#[IgnoreException]` attribute. For example the handler uses `#[IgnoreException(EventNotFoundException::class)]`. Whenever that exception is thrown, the exception is caught and ignored. The middleware thinks it's executed without issues.
The `HandlerDescriptor` class is final and therefore cannot be extended. To make this a bit more flexible we introduce the `HandlerDescriptorInterface` that is used everywhere. It also introduces a `.messenger.handler_descriptor_class` parameter that's used in the MessengerPass. With this parameter it's possible to use a custom implementation of the `HandlersLocator`. This is an alternative approach to symfony#50980.
Sure, this can be solved by a custom middleware. But that means that I will be replacing a big part of Messenger, namely the way it handles messages: HandleMessageMiddleware.php. That requires me to keep my "fork" middleware, sync with every release. What I want is an extension point, where it passes a message to more than 1 handlers. My use case: I want to add extra functionality for event subscribers. I want to know which subscriber failed, and which didn't. Or I want to wrap every subscriber inside its own database transaction. Currently, not possible as we can only wrap ALL handlers. |
A try/catch inside the subscribers will work, but that requires putting this in place in 1000's of subscribers. Or it requires me to create an abstract subscriber that all subscribers need to extend from. I want to handle this in a higher level. Abstracted away from what developers will see in their day to day. |
@ro0NL Interesting. I checked it but I don't see how that would work. Or how that could help my use case. I really think there should be an extension point for the execution of the handler. Or, in an ideal world, there is a dedicated middleware stacks per handler. That would allow wrapping handlers isolated, instead of the current situation where multiple handlers can be called. |
Bringing back to the attention 😎 . If we would make the At the same time, the risk/impact of such a change (private to protected) is limited, fairly safe to do so. Is this still an option to agree on and merge? 🙏 |
I made the following changes:
callHandler
protectedThis allows people to decorate the
HandleMessageMiddleware
and intercept the moment the handler is called.I currently have the following use cases for this in my application:
Every handler can decide if they want to wrap their handling in a transaction. This is done by
a custom
#[WrapInTransaction]
attribute. When the attribute is there, we wrap it in a databasetransaction. I know the
DoctrineTransactionMiddleware
exists in the Symfony Doctrine bridge,but that runs as middleware and cannot be toggled individually. It would be possible to modify it
so that it only runs when the handler has it enabled using an option, but that only works for
buses with only 1 handler. For an event bus, with multiple handlers, that won't be a solution.
Every handler can decide if they want to ignore certain exceptions that are thrown. This is done
by a custom
#[IgnoreException]
attribute. For example the handler uses#[IgnoreException(EventNotFoundException::class)]
. Whenever that exception is thrown, theexception is caught and ignored. The middleware thinks it's executed without issues.