I’m trying to get POST content in php side (using PHP 8.3, Symfony 7.1)
Here is an example backend:
#[Route("test")]
public function testing(Request $request) {
$_POST = json_decode($request->getContent(), true);
if(isset($_POST["line"])) { // FIRST METHOD
// try when using JS query (see code below)
}
if($request->request->get("line") != null) { // SECOND METHOD
// true when using Symfony queries
}
return $this->json([]);
}
Two if
are never true at the same time.
- The first method is multiple times recommended in stackoverflow (like here, here or here), and worked for me since long time
- The second method is mentionned in Symfony documentation
- When using this JS code:
await fetch("/test", {
method: "POST",
body: JSON.stringify({
"line": "the line"
}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
It works by using the first method $_POST
.
- When using this code with
PHPUnit
:
$client->request('POST', "/test", [
"line" => "the line"
], [], [ 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' ]);
It the second method that works (by using request->get
).
Why is there a difference and what am I doing wrong?
3
When you use $request->request->get("line")
Symfony is expecting application/x-www-form-urlencoded
as content type.
So you need to handle your body as a json in your symfony controller:
$line = json_decode($request->getContent(), true);
Or in symfony >= 5.2
$line = $request->toArray();
You could also modify your ajax request to be treated as application/x-www-form-urlencoded
.
You may also use of the new symfony 6 attribute to map your payload to a new dto in your method parameters (symfony >= 6.3).
2
What you are doing is wrong:
Nothing crazy needs to be done, it’s just the the approaches to send data, and each approach requires a different way to handle the data on the PHP side.
If you want to use the same approach for both scenarios, you could consider using a library like symfony/http-foundation
to handle the JSON requests in a more standardized way.
Here is the example on your code base to handle the request the PHPUnit test to send a raw JSON payload, similar to the JavaScript example, by using the json
key in the request
method.
$client->request('POST', "/test", [], [], [
'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json',
'json' => ['line' => 'the line']
]);
This would send a raw JSON payload; you could use the first method to handle it in your PHP code.
In your JavaScript example, you send the POST data as a JSON body:
- body is built by using
body: JSON.stringify()
- you specified the HTTP header
'Content-Type': 'application/json'
It means the HTTP request body has been sent as a JSON string:
{"line":"the line"}
That’s why your 1st option works, but you can see that you need to decode the JSON before any other operation: json_decode($request->getContent(), true)
. PHP is not built to handle POST data in JSON format by default.
The 2nd example is the standard one. By default, POST data is sent with a Content-Type
HTTP header with the value application/x-www-form-urlencoded
. The request body looks like a query string (like GET data):
line=the%20line
In this case, the $_POST
superglobal is already configured to contain a line
key, you don’t need to JSON decode anything. That’s why Symfony’s Request
(as it uses the $_POST
superglobal in its web runtime) can get your data using $request->request->get()
.
If you want your JS code and PHPUnit code to work, you should either:
- send the POST data in your JSON in the “default” format:
x-www-form-urlencoded
- encode the POST data from your PHPUnit test case in JSON (looks like you did not use the
request()
method properly)
PS: don’t create/rewrite superglobals like $_POST
, if not a bad practice, it’s at least very confusing. Never use such variables in Symfony anyways.
2