When sending a POST, I can fetch all the fields using $request->getParsedBody() and it works perfectly. With a similar PUT request, the body is null hence parsing fails.
This is a long story. Because it is so intertwined with HTTP and streams, I couldn't easily write a failing testcase... :cry:
$app->post('/test', function ($request, $response) {
var_dump($request->getParsedBody());
});
$app->put('/test', function ($request, $response) {
var_dump($request->getParsedBody());
});
Requests are sent using Google Chrome and with javascript:
var data = new FormData
data.append('foo', 'bar')
request('POST', '/test', data, function() {
console.log('POST success')
})
request('PUT', '/test', data, function() {
console.log('PUT success')
})
request (method, uri, data, callback) {
var request = new XMLHttpRequest()
request.open(method, uri, true)
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
callback(request);
}
}
request.send(data)
}
The POST does show an array ['foo' => 'bar'], the PUT is null. Checking php's input (php://input) marks clearly the input is there (also, Chrome devtools show this as expected). As Slim\Http\Body is a slight wrapper around streams, I started digging in Slim\Http\Request::getParsedBody():
public function getParsedBody()
{
echo "getParsedBody()\n";
if ($this->bodyParsed) {
echo "Has already parsed body\n";
return $this->bodyParsed;
}
if (!$this->body) {
echo "Has no body\n";
return;
}
$mediaType = $this->getMediaType();
$body = (string)$this->getBody();
if (isset($this->bodyParsers[$mediaType]) === true) {
echo "Has parser\n";
$parsed = $this->bodyParsers[$mediaType]($body);
if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) {
throw new RuntimeException('Request body media type parser return value must be an array, an object, or null');
}
$this->bodyParsed = $parsed;
} else {
echo "Has no parser\n";
}
echo "Ready to return body\n";
return $this->bodyParsed;
}
Output:
// POST: iterating double and 2nd time it has the body parsed?
getParsedBody()
Has no parser
Ready to return body
getParsedBody()
Has already parsed body
// PUT: no parser, single iteration
getParsedBody()
Has no parser
Ready to return body
OK, so after reading this again and digging through, you just find the root cause of it all in 1 second here :laughing:
if ($request->isPost() &&
in_array($request->getMediaType(), ['application/x-www-form-urlencoded', 'multipart/form-data'])
) {
// parsed body must be $_POST
$request = $request->withParsedBody($_POST);
}
Wrong! Actually, any request can have request body data (even GET!) and AFAIK there is no HTTP spec rule which prevents PUT data to send and body, no matter what the media type is.
This current implementation makes multipart/form-data unparseable for non-POST requests. Will check out if I can find a solution to add a parser for this type.
I think that code is correct: $_POST is empty if the method is not POST.
I think that what's missing is a parser to deal with multipart/form-data data in php://input when the method is not POST.
It should be noted if you are using the _METHOD overloads that, this is not an issue iirc.
@akrabat yeah see my first comment. It just took some time to figure out multipart/form-data is parsed via $_POST for POST requests, but in the end there is no multipart/form-data parser to parse the data for non-POST. Now I am checking out if it's possible to write a parser.
@geggleto well I think for the new FW you shouldn't need to send POST to simulate PUT if you're actually able to send PUT requests ;)
In my opinion it is not worth to implement a custom parser for a multipart/form-data encoded body. Instead, if Slim detects this content type in a PUT request it should return a response with 415 Unsupported Media Type.
application/x-www-form-urlencoded, application/json, ...Anyway, see the rejected parser in Symfony for a possible implementation: https://github.com/symfony/symfony/pull/10381
I think that if someone needs multipart/form-data in a PUT request, then they can write their own media type parser and register it with the Request object.
@micheh kinda weird answer imho. Slim is a general purpose framework, it should not be opinionated in the case it does not allow PUT or DELETE requests with multipart/form-data bodies. If you want to support all common HTTP verbs and support all common body encodings, it should be part of Slim.
Either way, I have done like @akrabat said already, it's now a custom parser in my own app. If anyone need this for their own use in the future, just ping me and I'll explain.
@juriansluiman I think it was summarised on that symfony issue as to why we shouldn't be handling this at the framework level here https://github.com/symfony/symfony/pull/10381#issuecomment-38717986
@juriansluiman Can you please explain by creating a blog post about it and sharing the link. I am having issues with parsing put requests in slim v3.
This is like cut->paste on osx. You can do all "mabo jambo" , do entire circus around inside and outside but it does not support simple function like multipart/form-data with PUT.
There are no explanation or discussion for that. You can run but you can't walk slow because it is inefficient.
@tabaktoni PHP doesn't support multipart/form-data for PUT. I'm unconvinced that we should write our own decoder in Slim unless PHP exposes theirs.
if you sending data from put method make sure to set the content type to application/x-www-form-urlencoded
You have to set header request Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded is no good if you want to send binary data...
If nothing else, this should have been described in the documentation. The caveat is not obvious.
We would love a PR!
Most helpful comment
@micheh kinda weird answer imho. Slim is a general purpose framework, it should not be opinionated in the case it does not allow PUT or DELETE requests with multipart/form-data bodies. If you want to support all common HTTP verbs and support all common body encodings, it should be part of Slim.
Either way, I have done like @akrabat said already, it's now a custom parser in my own app. If anyone need this for their own use in the future, just ping me and I'll explain.