5

Scenario 1 sending x-www-form-urlencoded data

POST /path HTTP/1.1
Content-Type: application/x-www-form-urlencoded

foo=bar

Running print_r($request->getParsedBody()); returns fine:

Array
(
    [foo] => bar
)

Running print_r($request->getBody()->getContents()); returns a string foo=bar


Scenario 2 sending application/json data

POST /path HTTP/1.1
Content-Type: application/json

{
    "foo": "bar"
}

Running print_r($request->getParsedBody()); returns an empty array. Array ( )

But, running print_r($request->getBody()->getContents()); returns fine:

{"foo":"bar"}


Is this expected behavior?

Meaning, if we're sending x-www-form-urlencoded data, we should use getParsedBody().

While getBody()->getContents() should be used if we're sending application/json?


Additional info:

Request object is created using:

$request = \Laminas\Diactoros\ServerRequestFactory::fromGlobals(
        $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
);
IMB
  • 15,163
  • 19
  • 82
  • 140
  • 1) Which PSR-7 library are you using - maybe zend diactoros? 2) To each scenario, you should provide the code for building the _ServerRequest_ object. 3) To "Scenario 1": Have you also tried activating the `StreamInterface::__toString` method with something like this: `$content = (string) ($request->getBody()); echo $content;`? I'm asking this because a `StreamInterface::__toString` method implementation _"MUST attempt to seek to the beginning of the stream before reading data"_, whereas `StreamInterface::getContents` only _"Returns the **remaining** contents in a string"_. – PajuranCodes Feb 08 '20 at 22:20
  • @dakis Actually scenario 1 seems to actually return a string `foo=bar` when using `$request->getBody()->getContents()`. I've update the scenarios. Also included how request object is created. – IMB Feb 09 '20 at 07:17

2 Answers2

10

Message body:

In a PSR-7 library, the message body is abstracted by the StreamInterface. Any implementation of this interface MUST wrap a PHP stream and, of course, should provide the proper functionality to perform the specific read/write/seek operations on it. PHP provides a list of I/O streams, from which php://input is suitable for the task in question.

php://input is a read-only stream that allows you to read raw data from the request body. php://input is not available with enctype="multipart/form-data".

In this context, when a request to the server is performed, the request body data (regardless of its data type) is automatically written to the php://input stream, in raw format (string). The information can later be read from it by calling StreamInterface::getContents, StreamInterface::__toString, or StreamInterface::read (which would probably make use of stream_get_contents(), or similar, in their implementation).

Note: The method StreamInterface::__toString is automatically called when the object representing the message body, e.g. the instance of the class implementing StreamInterface is cast to a string. For example, like this - see Type Casting in PHP:

$messageBodyObject = $request->getBody(); // implements StreamInterface
$contentOfMessageBody = (string) $messageBodyObject; // cast to string => StreamInterface::__toString is called

echo $contentOfMessageBody;

Parsed body:

In regard of the PSR-7, the parsed body is a "characteristic" of the applications where PHP is "used as a server-side application to fulfill HTTP requests" (in comparison with the applications where PHP is used as "an HTTP client") - see Summary of the PSR-7 Meta Document. So, the parsed body is a component of the ServerRequestInterface only.

The parsed body (read the comments of ServerRequestInterface::getParsedBody and ServerRequestInterface::withParsedBody) is thought as a representation in a "parsed" form (array, or object) of the raw data (a string) saved in the php://input stream as the result of performing a request. For example, the $_POST variable, which is an array, holds the parsed body of a POST request, under the conditions presented bellow.

Relevant use-cases:

If a POST request is performed and the header Content-Type is application/x-www-form-urlencoded (for example when submitting a normal HTML form), the content of the request body is automatically saved into both the php://input stream (serialized) and the $_POST variable (array). So, in the PSR-7 context, calling both StreamInterface::getContents (or StreamInterface::__toString, or StreamInterface::read) and ServerRequestInterface::getParsedBody will return "valid" values.

If a POST request is performed and the header Content-Type is multipart/form-data (for example when performing a file(s) upload), the content of the request body is NOT saved into the php://input stream at all, but only into the $_POST variable (array). So, in the PSR-7 context, only calling ServerRequestInterface::getParsedBody will return a "valid" value.

If a POST request is performed and the header Content-Type has other value than the two presented above (for example application/json, or text/plain; charset=utf-8), the content of the request body is saved only into the php://input stream. So, in the PSR-7 context, only calling StreamInterface::getContents (or StreamInterface::__toString, or StreamInterface::read) will return a "valid" value.

Resources:

PajuranCodes
  • 303
  • 3
  • 12
  • 43
  • (string) $request->getBody(); // returns raw json payload, as long as $request is implementing ServerRequestInterface – pofqggg Oct 07 '21 at 13:54
  • @pofqggg Hi. Thanks. But could you please be more precise in what you are trying to tell me/us? – PajuranCodes Oct 07 '21 at 18:14
  • just summarizing what you wrote on the topic. To get the raw payload from a request object, implementing ServerRequestInterface - (string) $request->getBody(); is the way – pofqggg Oct 08 '21 at 19:29
  • 1
    @pofqggg Nice suggestion. Thank you again. I completed my answer with it. – PajuranCodes Oct 08 '21 at 22:18
0

The answer by @dakis is correct, but I find it a bit ambiguous in answering the original question of why Scenario 2 failed.

From a PSR standpoint, the behaviour is correct (as @dakis said):

  • body returns a stream to the request body

  • parsedBody is a characteristic of the request and can contain a parsed representation of the body (but is not required to), as mentioned in the PHPDoc of ServerRequestInterface::getParsedBody:

    Otherwise, this method may return any results of deserializing the request body content; ...

From a usefulness perspective, laminas-diactoros is lacking and, in my opinion, half-baked. This library doesn't seem to do a lot more than passing around data already parsed by PHP ($_GET/$_POST..). A better implementation would have handled the specific content-type for use with parsedBody and would have automatically thrown or handled bad POST data.

Christian
  • 27,509
  • 17
  • 111
  • 155