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
[Form] PATCH requests should not succeed if constraints fail #11493
Comments
Very good bug report! Thanks for taking your time to write this down and prepare reproducible scenario. We'll have a look at it soon. |
Thanks, Jakub. Looking forward to seeing what the root cause of this is. It happens in both the legacy and new validation APIs. I tried tracking it down with no success. |
I just looked into it. The problem is with your submit call(). At first the currently recommended way is
When you replace your submit call with this you get the invalid statement. You can get some more information about the actual submit here: http://symfony.com/doc/current/cookbook/form/direct_submit.html You have submitted with the following call:
You can actually get this to work with
This issue seems to be the second parameter of submit(). It defines that missing data I figured out if you validate manually after the submit with
You get the proper error. |
The reason I am using submit() instead of handleRequest() is because I need to filter out extra fields. Currently, forms do not support extra fields. Due to constraints I have in my project, the client does not send me its request wrapped in any kind of envelope. That is: the form fields are not contained under a form name namespace. Thus, all my form names are null. If I use handleRequest, extra fields (such as _format) will cause the form to barf. Submit is my only option until forms support an option to ignore extra fields. As for the second parameter: the second parameter is clearMissing. In other words, if I don't send a field, the form should assume I mean to null it if $clearMissing = true. I don't want that behavior because I am supporting partial updates. I can't support partial updates if clearMissing = true. So yes, if I run the entity through the validator manually I get the proper error. If I enable clearMissing I get the proper error. But, I still believe (strongly) that this is a bug because it should still work with clearMissing = false. Can you convince me otherwise based on my requirements of supporting extra fields and needing to do partial updates? |
@peterrehm I have managed to far simplify the test case, now that better I understand what is going on. It seems that this bug is purely a matter of calling submit() with an empty request which causes the form to enter some sort of invalid internal state whereby isValid() will return true regardless of the values of the underlying data_class. Please see the updated test case: https://github.com/jmcclell/recursive-validator-bug-test-case This test case demonstrates it with a single entity object and two text fields. |
This is the change that introduced this bug/scenario. Because clearMissing = false and the data is empty, the field type (which is itself just a form) is not submitted, thus errors aren't applied to it because of conditions in the ValidationMapper which require that the form has been submitted. This is the original bug which describes a similar situation to mine. I think my bug is an edge case to that bug. In this bug, he wants to make sure that data that is set programmatically still gets validated even if it isn't submitted as part of the form. I also want this. The edge case I seem to have stumbled upon is that empty requests cause the form to not report any validation errors. I believe the proper behavior for a form which is bound to an entity would be to validate the underlying entity in its entirety for the given validation group(s). This works will full updates and it works with partial updates (PATCH) provided you load your initial entity from its source and use the form to override fields, which is what makes sense to me. What seems to be happening now is broken behavior, imho. It's possible that I'm not considering one or more other valid scenarios that my suggestion breaks, however. |
It is actually strange behaviour and I userstand your use case. These two change actually leads to the fact that unsubmitted forms are ignored: https://github.com/symfony/symfony/pull/10567/files#diff-7b14a00bf598c6870d7e3556f4bb4ff5R300 @webmozart Is this behaviour really intended? How about a setting to specifically define the behavior? I think usually the entire object should be validated, even if you submit only a few fields. @jmcclell A solution for the meantime would be to validate the object manually. Than you can ensure for your case to have valid objects. |
Thanks peterrehm. Currently I validate once at the controller level via the form so that I can return form errors back to the user as JSON (using FOSRestBundle in this API) and then I validate a second time at the service-level to catch this issue. It's not ideal but it affords me protection until the form behavior is sussed out. |
I don't know how the FOSRestBundle works exactly, but I think you could add some way to return only your custom validation errors in the controller as JSON. So this would make it easy for you to change the behavior if symfony changes. |
Hm, I wonder why I implemented #10567 the way I did. I guess idea was to enable PATCH requests on objects with failing constraints on non-submitted fields. However, this goes against the logic of the form's validation behavior otherwise, namely that an entity must leave the form in a valid state. Forms usually fail also if constraints are violated on fields that are not mapped in the form. I feel inclined to revert #10567 for that reason. If someone wants to PATCH an object, they must ensure that the object is in a valid state after the request by ensuring that the object is already valid before the PATCH operation:
Then any error after the PATCH must result from the patched data and hence requires correction. Letting an invalid object be returned from "valid" forms and persisting that object sounds completely irrational to me. I'll let this settle over the weekend. |
Aye. I agree with your current view on how the behavior should work. If I am to rely on the form component for validation, I need to be able to rely on it wholly and be able to safely assume that it will always honor all constraints. As for your original idea, perhaps there is another way to go about that, but I am not sure it is really necessary. I believe in most cases people will be loading the original entity anyway in order to perform the PATCH operation, and any entity that already exists should already be in a valid state. One would hope, at least. |
@webmozart I would agree to your comment that the object should be in a valid state before the patch or at least after the patch. |
I have found a work-around for some use-cases of this issue using a somewhat-dirty-hack. As of Symfony 2.4, if you set the fields you really want validated with This let your invalid objects be effectively invalid, but wrongs the validation error's location in the form tree. This results in a behavior somewhat similar to adding a superfluous In my case, the immediate parent form can be entirely ignored safely, hence I can stop the bubbling at this point. The validation error is then reported near the actual error location, which is the best I could manage to achieve. [EDIT] To circumvent this issue, you can use a event listener that forces the submission of /**
* $builder->add(...) the fields that MUST be validated properly
*/
$builder->addEventListener(
// PRE_SUBMIT lets you set the data to be submitted
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder) {
$submittedData = $event->getData();
// The already submitted data is kept unchanged
$dataToSubmit = $submittedData;
foreach ($builder->all() as $name => $subType) {
/** @var FormBuilderInterface $subType */
// Here, the 'required' value determine whether this field must be validated properly
if ($subType->getRequired() && !array_key_exists($name, $submittedData)) {
// Force submission of a null value, exactly the same way
// it would be done with $clearMissiing = false
$dataToSubmit[$name] = null;
}
}
$event->setData($dataToSubmit);
}
); This solution is only intended to be a temporary patch in use-cases where this issue is a blocker. |
@webmozart I agree with you that it does not make sense |
I'd add to this list the issues I've faced while trying to make GET requests to work: https://gist.github.com/guilhermeblanco/287e67fff9d1dcbc2f13 |
Any updates on this? This behaviour is still present as far as I am aware. |
+1 It's been more than two years this issue has been raised. With REST API being more and more used, lot of people with face this issue. |
…ted (egeloen) This PR was merged into the 2.7 branch. Discussion ---------- [Form] Consider a violation even if the form is not submitted | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | yes | New feature? | no | BC breaks? | yes (only for the behavior) | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11493 | License | MIT | Doc PR | Hey! I'm currently implementing an API using the form component in order to validate the payload sent (in conjonction with the FOSRestBundle). Unfortunatelly, we dig into an issue about the PATCH request which don't map some of our validation rules to the form. Basically, the violations are lost in the middle of the process. ### Use case We have an entity with the following fields "type", "image" & "video". The field "type"can be either "default", "image" or "video" and then accordingly we use the appropriate field (none for the "default" type, video for the "video" type and image for the "image" type. Then, in our form, we change the validation groups according to our entity type in order to make the "image" field mandatory if the type is "image" and the same for the video field if the type is "video". ### Current behavior The current behavior (since 2.5) seems to not propages a violation to a form if this form is not submitted but in our use case, changing the field "type" via a PATCH request triggers some new validation which should be reported to end user (inform that a field (video or image) is missing in the PATCH request). ### Expected behavior The current behavior was introduced in #10567 but IMO, this update is a bug as suggested by @webmozart in #11493 (comment) Instead, the form component should still map validation errors to the form even if the field was not submitted. If the initial data is not valid, then your initial data was buggy from the beginning but the form should not accept it and instead of silently ignoring the errors, end users should be informed and fix it. WDYT? Commits ------- c483a0f [Form] Consider a violation even if the form is not submitted
…ted (egeloen) This PR was merged into the 2.7 branch. Discussion ---------- [Form] Consider a violation even if the form is not submitted | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | yes | New feature? | no | BC breaks? | yes (only for the behavior) | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11493 | License | MIT | Doc PR | Hey! I'm currently implementing an API using the form component in order to validate the payload sent (in conjonction with the FOSRestBundle). Unfortunatelly, we dig into an issue about the PATCH request which don't map some of our validation rules to the form. Basically, the violations are lost in the middle of the process. ### Use case We have an entity with the following fields "type", "image" & "video". The field "type"can be either "default", "image" or "video" and then accordingly we use the appropriate field (none for the "default" type, video for the "video" type and image for the "image" type. Then, in our form, we change the validation groups according to our entity type in order to make the "image" field mandatory if the type is "image" and the same for the video field if the type is "video". ### Current behavior The current behavior (since 2.5) seems to not propages a violation to a form if this form is not submitted but in our use case, changing the field "type" via a PATCH request triggers some new validation which should be reported to end user (inform that a field (video or image) is missing in the PATCH request). ### Expected behavior The current behavior was introduced in #10567 but IMO, this update is a bug as suggested by @webmozart in symfony/symfony#11493 (comment) Instead, the form component should still map validation errors to the form even if the field was not submitted. If the initial data is not valid, then your initial data was buggy from the beginning but the form should not accept it and instead of silently ignoring the errors, end users should be informed and fix it. WDYT? Commits ------- c483a0f [Form] Consider a violation even if the form is not submitted
Can you confirm that the changes proposed in #30265 will fix this issue? |
…requests (xabbuh) This PR was merged into the 3.4 branch. Discussion ---------- [Form] do not validate non-submitted form fields in PATCH requests | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11493, #19788, #20805, #24453, #30011 | License | MIT | Doc PR | When a form field is not embedded as part of a HTTP PATCH requests, its validation constraints configured through the `constraints` option must not be evaluated. The fix from #10567 achieved this by not mapping their violations to the underlying form field. This however also means that constraint violations caused by validating the whole underlying data object will never cause the form to be invalid. This breaks use cases where some constraints may, for example, depend on the value of other properties that were changed by the submitted data. Commits ------- a60d802 do not validate non-submitted form fields in PATCH requests
If I construct two objects Foo and Bar, such that:
whereby Foo hasOne Bar, I would expect that a form constructed with a data_class of Foo would validate all properties of Foo which have constraints regardless of whether or not a default value for $bar is set. Instead, the form passes validation even if other fields in Foo which have constraints should fail, provided that I have set a default value for $foo->bar.
It's easier to demonstrate than explain:
https://github.com/jmcclell/recursive-validator-bug-test-case
In this repo, there is a single test class at
src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
with two test cases. One hits a route where I do not define a default value for Foo::bar and one where I do. Both test cases provide an empty request to the route. I am expecting a 400 response in both cases, but as you can see, where I set the default I get a 200 response, even though Foo::Baz does not have its constraints met.This means that if I were to place business logic inside the Form::isVaid() conditional which depends on the entities being validated, I am going to get entities that I cannot trust to be valid if the client sends a malformed (in this case, empty) request.
Curiously, if I run the object through the Validator manually, it performs as expected and returns the proper list of validation errors. It appears to be specific to form validation.
I ran into this bug with a specific setup of mine, so you'll notice a few things:
src/Acme/DemoBundle/Entity
folder for both theFoo
andBar
entitesThe text was updated successfully, but these errors were encountered: