Skip to content

HttpFoundation: Make request->get() behave more like $_REQUEST #40974

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

Closed
flack opened this issue Apr 28, 2021 · 13 comments
Closed

HttpFoundation: Make request->get() behave more like $_REQUEST #40974

flack opened this issue Apr 28, 2021 · 13 comments

Comments

@flack
Copy link
Contributor

flack commented Apr 28, 2021

The get method of Request currently looks at

  • custom routing params
  • GET
  • POST

in that order. It's a bit unfortunate, because in PHP's default config, the order in $_REQUEST is the opposite of that, i.e. a parameter in $_POST overrides the same parameter from $_GET in the $_REQUEST superglobal. Which seems more logical: Let's say a user opens some search URL, clicks through a few pages of results (typically the search params are in GET in that scenario), then changes a value in the form and submits (let's say the form POSTs by default for the sake of argument), the POST data gets ignored.

So I was wondering: Would you be open to make the order in the get function depend on the request_order (https://www.php.net/manual/en/ini.core.php#ini.request-order) ini setting? That would make $request->get('something') a drop-in replacement for $_REQUEST['something'] (well, almost at least).

Basically, it would look something like this:

    public function get(string $key, $default = null)
    {
        if ($this !== $result = $this->attributes->get($key, $this)) {
            return $result;
        }

        $order = explode('', ini_get('request_order'));

        foreach (array_reverse($order) as $item) {
            switch ($item) {
                case 'g':
                    if ($this->query->has($key)) {
                        return $this->query->all()[$key];
                    }
                    break;

                case 'p':
                    if ($this->request->has($key)) {
                        return $this->request->all()[$key];
                    }
                    break;

                case 'c':
                    if ($this->cookies->has($key)) {
                        return $this->cookies->all()[$key];
                    }
                    break;
            }
        }

        return $default;
    }
@stof
Copy link
Member

stof commented Apr 28, 2021

Changing the order would be a BC break (with no good way to warn about it).
And making the order dependent on the ini setting would make the order unknown to libraries (as they don't control the ini setting), which would make it a weird API.

If you want an utility method respecting the ini setting, you can implement it in your project as a function getting the Request object as argument. All parameter bags used in your implementation are available publicly.

@flack
Copy link
Contributor Author

flack commented Apr 28, 2021

yes, the BC break is problematic. It would definitely be better as a separate function, the only question is what would be a good name for it...

Regarding weird API: I would say not any weirder than PHP itself, and it's not like nothing else in Symfony changes when php.ini values are changed.

Also, I think the current implementation has significant footgun potential: Let's say you have some POST form on your site and decide to read the data with $request->get() (or maybe you use some library that uses that without you knowing). then I can trivially break your functionality by sending people links like "/contact-form?message=bla": For the user filling out the form, there's no way to tell that something is off, but whatever they put into the message field will get lost on submit

@flack
Copy link
Contributor Author

flack commented Apr 28, 2021

Or to make it more explicit: I send the user a link like /order?selectedproduct=ultra-deluxe-edition. User fills out the order form, selects the "free trial" product, and then gets a gigantic unexpected invoice. Looking at GET before POST opens you to all kinds of attacks, I can't currently think of a situation where it would be a good idea to do that

@ro0NL
Copy link
Contributor

ro0NL commented Apr 28, 2021

getting the wrong GET param can go equally wrong too. Note the default variable order in PHP is also GP.

IMHO the only sane answer is to deprecate Request::get()

@flack
Copy link
Contributor Author

flack commented Apr 28, 2021

@ro0NL yes, the default order is GP, and

Registration is done from left to right, newer values override older values.

so P takes precedence over G

@stof
Copy link
Member

stof commented Apr 28, 2021

@flack the fact that PHP itself has a weird API does not mean that Symfony should provide an equivalent. Note that by modern standards of PHP RFCs, the $_REQUEST superglobal with a behavior being dependant on an ini setting would probably be rejected.

Let's say you have some POST form on your site and decide to read the data with $request->get()

to me, that's the actual issue. If you know that you want to read a POST data, use the dedicated API doing that.

@stof
Copy link
Member

stof commented Apr 28, 2021

IMHO the only sane answer is to deprecate Request::get()

I would totally vote for it. In my own projects, usages of Request::get() have been banned 10 years ago.

@flack
Copy link
Contributor Author

flack commented Apr 28, 2021

to me, that's the actual issue. If you know that you want to read a POST data, use the dedicated API doing that.

Well, if you're using a library, you may not even be aware that you're using it. Also, see my original example with paginated search results. At least in sites I work on, pagination usually makes a query string out of the search form, and to do a new search, you would send the form again, which typically uses POST (at least from what I see). So I would say there's a usecase for POST with a fallback to GET, but not the other way around.

But yes, i would also vote to deprecate the current get function. And maybe make some new function that emulates $_REQUEST or some sane subset of it, to help out projects migrating from the dark ages to current design paradigms

@ro0NL
Copy link
Contributor

ro0NL commented Apr 28, 2021

adding something that emulates $_REQUEST doesnt help migrating away from it ;)

@stof
Copy link
Member

stof commented Apr 28, 2021

and to do a new search, you would send the form again, which typically uses POST (at least from what I see)

well, search forms are often implemented as <form method="GET> precisely because a search is something where it makes sense to use a GET request with the term in the query string (also, it makes the search result page potentially cacheable)

@flack
Copy link
Contributor Author

flack commented Apr 28, 2021

well, search forms are often implemented as <form method="GET> precisely because a search is something where it makes sense to use a GET request with the term in the query string (also, it makes the search result page potentially cacheable)

yeah, like I said, that's mostly from sites I see (or work on), and the rationale for POST in the form is precisely to avoid hitting a cache.

Anyhow, should I make a "Deprecate Request::get()" ticket? If nobody likes my $_REQUEST emulation, I can also implement it on my side, it's only a few lines anyways :-)

@flack
Copy link
Contributor Author

flack commented Apr 29, 2021

closing in favor of #40984

@flack flack closed this as completed Apr 29, 2021
@gggeek
Copy link

gggeek commented May 2, 2021

My 2c: search forms in an ideal world should use GET 99% of the time, but sometimes they hit max length of URL allowed by browsers, proxies, reverese-proxies or webservers, esp. when adding in faceting. This is often found after the initial build, and quick-fix is then to switch the search form to POST, possibly even in JS. This is one of the cases where being able to conflate GET with POST on the receiving end actually makes sense ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants