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

Fix return value from CRUDController::getRestMethod() respecting Request::getHttpMethodParameterOverride() #6320

Merged
merged 1 commit into from Oct 19, 2020

Conversation

phansys
Copy link
Member

@phansys phansys commented Aug 25, 2020

Subject

Fix return value from CRUDController::getRestMethod() respecting Request::getHttpMethodParameterOverride().

I am targeting this branch, because this change respects BC.

Closes #6046.
Closes #6098.

Changelog

### Fixed
- Fixed return value from `CRUDController::getRestMethod()` respecting `Request::getHttpMethodParameterOverride()`.

### Deprecated
- Deprecated `Sonata\AdminBundle\Controller\CRUDController::getRestMethod()` method in favor of `Symfony\Component\HttpFoundation\Request::getMethod()`.

@phansys phansys added the patch label Aug 25, 2020
@phansys phansys requested a review from a team August 25, 2020 15:06
@phansys phansys marked this pull request as ready for review August 25, 2020 15:14
@phansys
Copy link
Member Author

phansys commented Aug 25, 2020

@sonata-project/contributors, do you know why we need this method instead of trusting Request::getMethod() directly?

if (!$method && self::$httpMethodParameterOverride) {
    $method = $this->request->get('_method', $this->query->get('_method', 'POST'));
}

AFAIK, this is only avoiding to use the logic defined there, like falling back to $this->query->get('_method', 'POST') or using X-HTTP-METHOD-OVERRIDE header.
Since it's not clear to me, is not easy to create valid test cases.
Any clue is welcome.

@phansys
Copy link
Member Author

phansys commented Aug 25, 2020

Reading the history, maybe this method is not required anymore.

@phansys phansys force-pushed the pr_6098 branch 2 times, most recently from fac5c9f to fbd71fe Compare August 25, 2020 19:43
@phansys phansys added minor and removed patch labels Aug 25, 2020
@phansys phansys force-pushed the pr_6098 branch 3 times, most recently from 5a37258 to 39c4e24 Compare August 26, 2020 08:51
Copy link
Member

@wbloszyk wbloszyk left a comment

Choose a reason for hiding this comment

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

Request::getMethod() will return $_SERVER['REQUEST_METHOD'] with some hack for POST where are additional checking and return if exist in some order:

  1. $this->headers->get('X-HTTP-METHOD-OVERRIDE');
  2. $httpMethodParameterOverride === true
    • $_POST['_method']
    • $_GET['_method']
    • 'POST'

Technicly exist two solution:
1.

Request::enableHttpMethodParameterOverride()
if (Request::METHOD_DELETE === $request->getMethod()) {

or

if (
    Request::METHOD_POST === $request->getMethod() &&
    'DELETE' === $request->request->get('_method')
) {

For better understand it look at Symfony\Component\HttpFoundation\Request::getMethod()

Comment on lines +1116 to +1117
Request::enableHttpMethodParameterOverride();

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Request::enableHttpMethodParameterOverride();

This should not be enabled in tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you propose to cover the expected behavior then?
Additionally, please note that the original value for Request::$httpMethodParameterOverride is being restored after each individual test:

protected function tearDown(): void
{
parent::tearDown();
if (!$this->httpMethodParameterOverride && Request::getHttpMethodParameterOverride()) {
$disableHttpMethodParameterOverride = \Closure::bind(static function (): void {
self::$httpMethodParameterOverride = false;
}, null, Request::class);
$disableHttpMethodParameterOverride();
}
}

Copy link
Member

Choose a reason for hiding this comment

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

This should be done in Action, otherwise test will work and action do not:

if (Request::METHOD_DELETE === $request->getMethod()) {

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'm sorry @wbloszyk, I don't understand your suggestion or why you say it won't work.
AFAIK, the choice about enabling or disabling the http method overriding is a global responsibility in the context of the whole application execution. By instance, the configuration node framework.http_method_override establishes this setting globally.

Copy link
Member

Choose a reason for hiding this comment

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

https://symfony.com/doc/2.6/book/forms.html#book-forms-changing-action-and-method

NOTE
If the form’s method is not GET or POST, but PUT, PATCH or DELETE, Symfony will insert a hidden field with the name _method that stores this method. The form will be submitted in a normal POST request, but Symfony’s router is capable of detecting the _method parameter and will interpret it as a PUT, PATCH or DELETE request. Read the cookbook chapter “How to Use HTTP Methods beyond GET and POST in Routes” for more information.

When $disableHttpMethodParameterOverride === false then

if (Request::METHOD_DELETE === $request->getMethod()) { } // always false
if (Request::METHOD_DELETE === $request->getRestMethod()) { } // always false, before this change - true

Copy link
Member

Choose a reason for hiding this comment

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

Otherwise Request::enableHttpMethodParameterOverride() will be not nesessery in tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

https://symfony.com/doc/2.6/book/forms.html#book-forms-changing-action-and-method

I'm not sure I understand what are you trying to expose, but I think we should start from the fact that Request::$httpMethodParameterOverride is a property that enables the Request::getMethod() to return other values than the real HTTP method ($_SERVER['REQUEST_METHOD']), based on the "_method" param provided in the $_POST or $_GET superglobals or in the "X-HTTP-METHOD-OVERRIDE" header.
Regarding this doc entry, be aware that FormConfigBuilder::setMethod() is setting this "_method" parameter.

Otherwise Request::enableHttpMethodParameterOverride() will be not nesessery in tests.

If you run these tests, you'll see that calling Request::enableHttpMethodParameterOverride() is necessary to achieve the expected result.

Maybe I'm not understanding the scenario you are talking about.
If that is the case or there is something else I'm omitting or misunderstanding about your concern, please try to share a test case or a alternative PR with what you think should be done. That way I think we could get a clearer perspective.

Copy link
Member

Choose a reason for hiding this comment

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

If you run these tests, you'll see that calling Request::enableHttpMethodParameterOverride() is necessary to achieve the expected result.

So code is working when Request::$httpMethodParameterOverride is enabled and don't when it is disabled. It is what I mean. I think current code is working correct and we should consider #6046 one more time.

@SonataCI
Copy link
Collaborator

Could you please rebase your PR and fix merge conflicts?

@VincentLanglet
Copy link
Member

@phansys @wbloszyk What's the status of this ?

@phansys
Copy link
Member Author

phansys commented Oct 18, 2020

@phansys @wbloszyk What's the status of this ?

IIUC the issue, this PR is RTM.
@wbloszyk, if you think other approach must be followed, please try to provide a new PR in order to check what you mean, since I'm not understanding the point in your comments.

@VincentLanglet VincentLanglet requested a review from a team October 18, 2020 21:29
@@ -217,7 +217,7 @@ public function deleteAction($id) // NEXT_MAJOR: Remove the unused $id parameter
return $preResponse;
}

if (Request::METHOD_DELETE === $this->getRestMethod()) {
if (Request::METHOD_DELETE === $request->getMethod()) {
Copy link
Member

@wbloszyk wbloszyk Oct 19, 2020

Choose a reason for hiding this comment

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

@phansys In this case, when people will have this parameter disabled then this if will be treated as allways false.

Case 1:

framework:
    http_method_override: true

All forms will be generated as GET|POST (Request::getMethod()). If you want use DELETE then you should take it from _method parameter.

Case 2:

framework:
    http_method_override: false

Form attribute method will be override. If you want use DELETE then you can take it from Request::getMethod().

Copy link
Member Author

Choose a reason for hiding this comment

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

AFAIK, the "_method" request parameter must only receive a special treatment when Request::$httpMethodParameterOverride is set to true, otherwise it is a common parameter like any other:

HTML forms only support GET and POST methods. If you’re calling a route with a different method from an HTML form, add a hidden field called _method with the method to use (e.g. ). If you create your forms with Symfony Forms this is done automatically for you.

If the form’s method is not GET or POST, but PUT, PATCH or DELETE, Symfony will insert a hidden field with the name _method that stores this method. The form will be submitted in a normal POST request, but Symfony’s routing is capable of detecting the _method parameter and will interpret it as a PUT, PATCH or DELETE request. See the http_method_override option.

http_method_override¶
type: boolean default: true

This determines whether the _method request parameter is used as the intended HTTP method on POST requests. If enabled, the Request::enableHttpMethodParameterOverride method gets called automatically. It becomes the service container parameter named kernel.http_method_override.

Copy link
Member

@wbloszyk wbloszyk Oct 19, 2020

Choose a reason for hiding this comment

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

Correct, my bad here. It is little confusing. By default Request::$httpMethodParameterOverride is set to true. It mean it should not be set to true in tests. But as standalone component Request::$httpMethodParameterOverride is set to false. In test ,where Request is not taken from kernel, this parameter must be override.

protected static $httpMethodParameterOverride = false;

@wbloszyk wbloszyk closed this Oct 19, 2020
@wbloszyk wbloszyk reopened this Oct 19, 2020
@VincentLanglet VincentLanglet merged commit a86923d into sonata-project:3.x Oct 19, 2020
@VincentLanglet
Copy link
Member

Thanks @phansys

@phansys phansys deleted the pr_6098 branch October 19, 2020 11:20
@VincentLanglet VincentLanglet mentioned this pull request Aug 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

deleteAction does not do it
6 participants