-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Messenger] Added a middleware that validates messages #26648
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
<service id="messenger.middleware.validator" class="Symfony\Component\Messenger\Middleware\ValidatingMiddleware"> | ||
<argument type="service" id="validator" /> | ||
|
||
<tag name="message_bus_middleware" priority="8" /> |
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.
Any reasoning for such priority? I'd argue it should be higher by default as it would be one of the most important "first steps".
* @author Magnus Nordlander <[email protected]> | ||
* @author Tobias Nyholm <[email protected]> | ||
*/ | ||
class ValidatingMiddleware implements MiddlewareInterface |
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.
ValidationMiddleware
feels better to me and is more coherent with the other middleware names 🤔 (except LoggingMiddleware
🙊)
} | ||
} | ||
|
||
$next($message); |
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.
Don't forget the return
here.
*/ | ||
class ValidatingMiddleware implements MiddlewareInterface | ||
{ | ||
protected $validator; |
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.
Why would this one be protected
? I'd close first if we can so private
, nah?
The only remaining question is "Should this validation be enabled by default" (i.e. shouldn't we drop the To me, it sounds like we should enable it by default because:
What do you think? /cc @symfony/deciders |
daf3610
to
4889be7
Compare
I've squashed my commits and added validation by default. Validating a message without validation metadata will always pass. |
parent::__construct($this->__toString()); | ||
} | ||
|
||
public function __toString() |
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.
AFAIK, we never defined __toString
method on exceptions in the core. Not sure this is desired now.
return $this->violatingMessage; | ||
} | ||
|
||
public function getViolations() |
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 return type?
protected $violations; | ||
protected $violatingMessage; | ||
|
||
public function __construct($violatingMessage, ConstraintViolationListInterface $violations) |
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 a @param object $violatingMessage
docblock?
class ValidationMiddleware implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var ValidatorInterface |
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.
Can be removed as there is already a typehint on the constructor
04d3572
to
d7f72c0
Compare
Thank you for the review. I've updated accordingly. I've also rebased the PR on master. |
$this->violatingMessage = $violatingMessage; | ||
$this->violations = $violations; | ||
|
||
parent::__construct($this->__toString()); |
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.
- parent::__construct($this->__toString());
+ parent::__construct(sprintf("Message of type %s failed validation", get_class($this->violatingMessage));
as the __toString
method is now removed?
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.
Oh, Thanks
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.
Almost there, thanks for the work.
I slept on it and I believe we need an opt-out. It doesn't need to be complicated: a boolean at framework.messenger.validation
would do it. Could you add that?
|
||
/** | ||
* @param object $violatingMessage | ||
* @param ConstraintViolationListInterface $violations |
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.
We don't need 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.
Okey. Thanks
I agree. But there will be plenty of merge conflicts. I will add "opt-out" config once #26647 is merged. |
bac60a9
to
ab6c055
Compare
I've rebased my PR and added a way to opt-out from message validation |
->end() | ||
->end() | ||
->arrayNode('validation') | ||
->canBeDisabled() |
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.
- ->canBeDisabled()
+ ->{!class_exists(FullStack::class) && class_exists(Validation::class) ? 'canBeDisabled' : 'canBeEnabled'}()
so it does not appear as enabled when the validator component isn't installed?
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.
Excellent idea. I was looking for something like this. And if someone tries to use validation without the validation component I'll throw an exception.
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.
Indeed, we use to throw such exceptions from the FrameworkExtension :)
class ValidationFailedException extends \RuntimeException implements ExceptionInterface | ||
{ | ||
protected $violations; | ||
protected $violatingMessage; |
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.
if no inheritance use case for these properties in core then they should be private
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.
Thank you
*/ | ||
public function __construct($violatingMessage, ConstraintViolationListInterface $violations) | ||
{ | ||
$this->violatingMessage = $violatingMessage; |
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 it really needed? I'm used to handle one command at a time so I always have the message at hand, am I missing a use case? The existing NoHandlerForMessageException
doesn't expose it
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.
The difference is that the NoHandlerForMessageException
is a logic exception (or at least, should be to me), it is meant to fail badly. No need to access the exception programmatically and retrieve the issuing message.
ValidationFailedException
is a runtime exception, meant to be caught and handled/transformed to a better representation (e.g: an API problem response). It can also be used in a profiler panel or whatever :)
Anyway, even if we often handle one message at the time, use-cases with multiple messages do exist. We should cover them. 👍
$this->violatingMessage = $violatingMessage; | ||
$this->violations = $violations; | ||
|
||
parent::__construct(sprintf('Message of type "%s" failed validation', get_class($this->violatingMessage))); |
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 the end 😉
Thank you for the reviews. I've updated the PR accordingly |
…ception (ogizanagi) This PR was merged into the 4.1-dev branch. Discussion ---------- [Messenger] Make NoHandlerForMessageException a logic exception | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | no | New feature? | no <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | symfony/symfony#26648 (comment) <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | N/A <!-- required for new features --> To me, a missing handler for a message is a program logic exception (or misconfiguration). Even if it's only detected at runtime here. It's the same as `ServiceNotFoundException` which even is an `\InvalidArgumentException`. Could eventually also be the case here. Commits ------- 0489bbd948 [Messenger] Make NoHandlerForMessageException a logic exception
…ception (ogizanagi) This PR was merged into the 4.1-dev branch. Discussion ---------- [Messenger] Make NoHandlerForMessageException a logic exception | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | no | New feature? | no <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | #26648 (comment) <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | N/A <!-- required for new features --> To me, a missing handler for a message is a program logic exception (or misconfiguration). Even if it's only detected at runtime here. It's the same as `ServiceNotFoundException` which even is an `\InvalidArgumentException`. Could eventually also be the case here. Commits ------- 0489bbd [Messenger] Make NoHandlerForMessageException a logic exception
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.
Can you add some tests? That it adds (and not if disabled) the middleware.
|
||
if ($config['middlewares']['validation']['enabled']) { | ||
if (!$container->has('validator')) { | ||
throw new LogicException('The Validation middleware is only available when the validator component is installed and enabled. Try running "composer require symfony/validator".'); |
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.
- validator component
+ Validator component
3111acc
to
a1f086f
Compare
I've fixed the typo and added tests. I've also rebased on master. The Travis tests failure seams unrelated. |
@fabpot as you disagreed with the Doctrine one... WDYT about this one? 🤔 |
This one is fine as this is about integrating various Symfony components. |
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.
@Nyholm can you squash and rebase?
@sroze no need to rebase (as there are no merge commits) and no need to rebase (as there are no conflicts). Our gh tools does that automatically :) |
@fabpot that rebase request was meant so that we would have green ticks when I |
a1f086f
to
43a5171
Compare
Thank you @Nyholm. |
… (Nyholm) This PR was squashed before being merged into the 4.1-dev branch (closes #26648). Discussion ---------- [Messenger] Added a middleware that validates messages | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | coming This is inspired by [Fervo](https://github.com/fervo/ValidatedMessage). It runs the validator over messages implementing the `ValidatedMessageInterface`. Commits ------- 43a5171 [Messenger] Added a middleware that validates messages
One of the failing tests was actually related. Fixed in #26782. |
Thank you for merging |
This is inspired by Fervo. It runs the validator over messages implementing the
ValidatedMessageInterface
.