Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[HttpFoundation] Add support for parsing non-POST requests using request_parse_body() (PHP 8.4)#59358

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
rottifant wants to merge4 commits intosymfony:7.4
base:7.4
Choose a base branch
Loading
fromrottifant:7.3

Conversation

rottifant
Copy link

@rottifantrottifant commentedJan 3, 2025
edited
Loading

QA
Branch?7.3
Bug fix?no
New feature?yes
Deprecations?no
IssuesFix#59331
LicenseMIT

Introduce initial support for parsingmultipart/form-data in PUT/PATCH requests using PHP 8.4'srequest_parse_body() function.
This function can also be used forapplication/x-www-form-urlencoded.

Problems & Todos:

  • request_parse_body() is only available PHP >= 8.4.0 --> Backwards compatibility?
  • The existing testsfail (in environment with PHP 8.4) becauseapplication/x-www-form-urlencodedwill also use the new implementation. They should be adapted?
  • New tests formultipart/form-data should be implemented.
  • Check ifrequest_parse_body() is good increateFromGlobals() or if it should be implemented somewhere else (too?)?
  • Define the behavior when an exception is throw by the function

Unfortunately I could not find a way to test the new implementation properly.
Looking forward to your ideas & feedback, thx.

depsimon reacted with thumbs up emoji
…ateFromGlobals()Introduce initial support for parsing `multipart/form-data` in PUT/PATCH requests using PHP 8.4's `request_parse_body()` function.Known issue: Some tests fail due to missing Content-Type handling and lack of proper test setup for PUT/PATCH requests. These will be addressed in follow-up changes.
@carsonbot
Copy link

Hey!

I see that this is your first PR. That is great! Welcome!

Symfony has acontribution guide which I suggest you to read.

In short:

  • Always add tests
  • Keep backward compatibility (seehttps://symfony.com/bc).
  • Bug fixes must be submitted against the lowest maintained branch where they apply (seehttps://symfony.com/releases)
  • Features and deprecations must be submitted against the 7.3 branch.

Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change.

When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor!
If this PR is merged in a lower version branch, it will be merged up to all maintained branches within a few days.

I am going to sit back now and wait for the reviews.

Cheers!

Carsonbot

@rottifantrottifant marked this pull request as draftJanuary 3, 2025 22:55
@symfonysymfony deleted a comment fromcarsonbotJan 4, 2025
@OskarStark
Copy link
Contributor

You need to add a check for the php version then if it is only available in 8.4

@rottifant
Copy link
Author

The problem is that the tests fail because they were designed for the old solution with$request->getContent(). Additionally, the existing tests only cover requests tests forapplication/x-www-form-urlencoded, notmultipart/form-data.
The exception thrown originates fromrequest_parse_body():RequestParseBodyException: Request does not provide a content type because (as far as I understand) it is a test environment without a real request.
Setting$_SERVER['CONTENT_TYPE'] has no effect.

At the moment, I’m unsure how to properly adapt or rewrite the tests to handle this scenario. I would appreciate any suggestions or ideas on how to proceed with testing for the new implementation (and withmultipart/form-data) in this context.

@rottifant
Copy link
Author

I did some "manual tests" with a simple controller to see what the output is.

The controller:

class TestController{    #[Route('/test')]publicfunctiontest(Request$request):JsonResponse    {returnnewJsonResponse(['method' =>$request->getMethod(),'content_type' =>$request->headers->get('content-type'),'request_all' =>$request->request->all(),'request_get_content' =>$request->getContent(),'payload_all' =>$request->getPayload()->all(),'files_all' =>$request->files->all(),        ]);    }}

The requests (sent with cURL):

curl --location --request PUT 'http://127.0.0.1:8000/test' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'foo=foovalue' \--data-urlencode 'bar=barvalue'
curl --location --request PUT 'http://127.0.0.1:8000/test' \--form 'foo="foovalue"' \--form 'bar="barvalue"' \--form 'baz=@"/Users/rott/textfile.txt"'

with textfile.txt:
Symfony is cool


MethodContent-TypeConclusionResult with old implementationResult with new implementation
POSTx-www-form-urlencodednothing different{"method":"POST","content_type":"application\/x-www-form-urlencoded","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"foo=foovalue\u0026bar=barvalue","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":[]}{"method":"POST","content_type":"application\/x-www-form-urlencoded","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"foo=foovalue\u0026bar=barvalue","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":[]}
PUTx-www-form-urlencodednothing different{"method":"PUT","content_type":"application\/x-www-form-urlencoded","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"foo=foovalue\u0026bar=barvalue","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":[]}{"method":"PUT","content_type":"application\/x-www-form-urlencoded","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"foo=foovalue\u0026bar=barvalue","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":[]}
POSTform-datanothing different{{"method":"POST","content_type":"multipart\/form-data; boundary=--------------------------430970386763090088183934","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":{"baz":{}}}{"method":"POST","content_type":"multipart\/form-data; boundary=--------------------------907910549111218239990088","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":{"baz":{}}}
PUTform-dataWith the new implementation:
1. The body is parsed and files are available (while before the request was only available unparsed via$request->getContent())
2. The usage of$request->getPayload()->all() is also possible andSymfony\Component\HttpKernel\Exception\BadRequestHttpException is also not thrown anymore.
Important:$request->getPayload()->all() was removed from the response in this case because it threwSymfony\Component\HttpKernel\Exception\BadRequestHttpException: "Could not decode request body."

{"method":"PUT","content_type":"multipart\/form-data; boundary=--------------------------503204391586777501976026","request_all":[],"request_get_content":"----------------------------503204391586777501976026\r\nContent-Disposition: form-data; name=\u0022foo\u0022\r\n\r\nfoovalue\r\n----------------------------503204391586777501976026\r\nContent-Disposition: form-data; name=\u0022bar\u0022\r\n\r\nbarvalue\r\n----------------------------503204391586777501976026\r\nContent-Disposition: form-data; name=\u0022baz\u0022; filename=\u0022textfile.txt\u0022\r\nContent-Type: text\/plain\r\n\r\nSymfony is cool\r\n----------------------------503204391586777501976026--\r\n","files_all":[]}
{"method":"PUT","content_type":"multipart\/form-data; boundary=--------------------------249741139464139291005574","request_all":{"foo":"foovalue","bar":"barvalue"},"request_get_content":"","payload_all":{"foo":"foovalue","bar":"barvalue"},"files_all":{"baz":{}}}

@MatTheCat
Copy link
Contributor

Setting$_SERVER['CONTENT_TYPE'] has no effect.

Indeed you’d need$_SERVER['HTTP_CONTENT_TYPE']:https://www.php.net/manual/en/reserved.variables.server.php#refsect1-reserved.variables.server-description

@rottifant
Copy link
Author

Setting$_SERVER['CONTENT_TYPE'] has no effect.

Indeed you’d need$_SERVER['HTTP_CONTENT_TYPE']:https://www.php.net/manual/en/reserved.variables.server.php#refsect1-reserved.variables.server-description

Thanks for the notice, but unfortunately this also doesn't change it.
Even if I set$_SERVER['HTTP_CONTENT_TYPE'] right before callingrequest_parse_body()RequestParseBodyException: Request does not provide a content type is thrown.

I am a little bit confused because$_SERVER['CONTENT_TYPE'] is used in the test:

$_SERVER['CONTENT_TYPE'] ='application/x-www-form-urlencoded';

@MatTheCat
Copy link
Contributor

Woops indeed my bad I missedf16e206 😅

rottifant reacted with thumbs up emoji

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment

Choose a reason for hiding this comment

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

request_parse_body() is only available PHP >= 8.4.0 --> Backwards compatibility?

No need for a polyfill IMHO, upgrading PHP is the way to go to get support for this.

The existing tests fail (in environment with PHP 8.4) because application/x-www-form-urlencodedwill also use the new implementation. They should be adapted?

We should split the failing test cases and skip them on PHP 8.4

New tests for multipart/form-data should be implemented.

Given the way the PHP 8.4 function is implemented, this should be done using aphp -S subprocess

Check if request_parse_body() is good in createFromGlobals() or if it should be implemented somewhere else (too?)?

Looks good to me.

if (
\in_array($method, ['PUT', 'DELETE', 'PATCH'], true)
&& (
str_starts_with($contentType, 'application/x-www-form-urlencoded')

Choose a reason for hiding this comment

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

I'd let this check on the content-type to the request_parse_body function, no need to implement it ourselves

Copy link
Author

@rottifantrottifantJan 5, 2025
edited
Loading

Choose a reason for hiding this comment

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

What do you mean?
A content type check is required because if, for example, a request with content-typeapplication/json is passed to the function, it will throwRequestParseBodyException: Content-Type "application/json" is not supported. Or are we going to catch and handle it?RequestParseBodyException is also thrown with invalid content, not just unsupported.

Choose a reason for hiding this comment

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

I mean the check for the content-type: we can catch the exception and deal with it instead of checking the content-type ourselves (and diverging from the native check).
We have to add logic to deal with the exception anyway, we cannot let it bubble down unhandled (createFromGlobals isn't expected to throw such exceptions).

Copy link
Author

@rottifantrottifantJan 5, 2025
edited
Loading

Choose a reason for hiding this comment

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

Sorry, my bad. I overlooked the "on the content-type" in your message...
I checked this becauseRequestParseBodyException is also thrown with invalid content, not just unsupported Content-Type. Or are we going to differentiate this?

@nicolas-grekas
Copy link
Member

Oh, and we need to define the behavior when an exception is throw by the function!

@rottifant
Copy link
Author

@nicolas-grekas Thank you for the input.
What would you suggest to do when an Exception is thrown? Just leave request and files empty?

@nicolas-grekas
Copy link
Member

nicolas-grekas commentedJan 5, 2025
edited
Loading

I guess so. We should behave as close as how PHP behaves with other methods.

@MatTheCat
Copy link
Contributor

MatTheCat commentedJan 7, 2025
edited
Loading

Just a thought I had: wouldn’t the Runtime component be a better place forrequest_parse_body? As its goal is allegedly to populate$_POST and$_FILES it feels like it should be called beforeRequest::createFromGlobals?

@rottifant
Copy link
Author

Just a thought I had: wouldn’t the Runtime component be a better place forrequest_parse_body? As its goal is allegedly to populate$_POST and$_FILES it feels like it should be called beforeRequest::createFromGlobals?

Good point, but another thought from me: I originally came across this via Laravel, which is using Symfony as foundation for requests. For instance, Laravel relies on Symfony’s Request class for parsing requests. If this functionality were implemented in the Request class, Laravel and other frameworks leveraging Symfony’s components could benefit from it as well, given that they don’t rely on the Runtime component, as far as I understand

nicolas-grekas and PatrickePatate reacted with thumbs up emoji

@nicolas-grekas
Copy link
Member

Up to move this PR forward@rottifant ?

@rottifant
Copy link
Author

Up to move this PR forward@rottifant ?

Yes, I will continue in the next days.

j-schumann reacted with thumbs up emoji

@florian2peaches
Copy link

What's the current problem? I would love to have this fix/feature, maybe I can help.

@rottifant
Copy link
Author

@florian2peaches
I'm sorry. I am currently working on many projects and I don't have the time.

I would be very happy if you could take it over :-) The implementation itself seems to work. Test tests that cover this new functionrequest_parse_body still need to be added. I can give you access to my fork if you want.

florian2peaches reacted with thumbs up emoji

@florian2peaches
Copy link

The exception thrown originates from request_parse_body(): RequestParseBodyException: Request does not provide a content type because (as far as I understand) it is a test environment without a real request.
Setting $_SERVER['CONTENT_TYPE'] has no effect.

That's still turning CI red, any more info on that?

@rottifant
Copy link
Author

The exception thrown originates from request_parse_body(): RequestParseBodyException: Request does not provide a content type because (as far as I understand) it is a test environment without a real request.
Setting $_SERVER['CONTENT_TYPE'] has no effect.

That's still turning CI red, any more info on that?

Yes. As far as I found out, there is currently no way to test and simulaterequest_parse_body without a HTTP real request.
I thought about starting a server (php -S) when testing and sending real HTTP Requests to it to test the implementation. But tbh I do not have much experience with PHPUnit, don't exactly know how this should be done and if it is even possible to start the server in the test – especially with different environments/OS.

@florian2peaches
Copy link

I'm on the same lane, I know php works that way sorequest_parse_body internally accesses the request, but isn't there a way to inject a request internally or some kind of polyfill forrequest_parse_body?

@rottifant
Copy link
Author

Unfortunately that was also the point where I got stuck and had no other idea than the "real" http server. Maybe the Symfony team has an idea how to implement this in the tests?
As the function itself is relatively new, I could not find any information on how to test it properly.

@MrMeshok
Copy link

In php-src there are helper functions which start and send requests to built-in cli server.
You can look at example herephp_cli_server_005.phpt
And these helper functions live herephp_cli_server.inc

So in theory you can start simple server which callsRequest::createFromGlobals() and dumps result in response which you can then assert

@fabpotfabpot modified the milestones:7.3,7.4May 26, 2025
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@fabpotfabpotfabpot left review comments

@nicolas-grekasnicolas-grekasnicolas-grekas left review comments

@OskarStarkOskarStarkOskarStark left review comments

Assignees
No one assigned
Projects
None yet
Milestone
7.4
Development

Successfully merging this pull request may close these issues.

Support for multipart/form-data in non-POST requests using request_parse_body() (PHP 8.4)
8 participants
@rottifant@carsonbot@OskarStark@MatTheCat@nicolas-grekas@florian2peaches@MrMeshok@fabpot

[8]ページ先頭

©2009-2025 Movatter.jp