PHP 8.4: New request_parse_body function
PHP automatically parses HTTP POST requests to populate the $_POST and $_FILES super global variables. However, other HTTP requests with methods such as PUT and PATCH do not get parsed automatically, and it is up to the PHP application to parse the request data.
With the popularity of the REST APIs that increasingly utilize HTTP methods such as PUT, DELETE, and PATCH, parsing HTTP request data consistently is important. However, starting to automatically parse HTTP request data for non-POST requests can be a breaking change for existing PHP applications.
PHP provides a stream wrapper at php://input, that contains the request data. For POST requests with enctype="multipart/form-data", this stream wrapper remains empty because it's automatically parsed and consumed to populate $_POST and $_FILES variables. The automatic $_POST/$_FILES processing can be controlled with the enable_post_data_reading=Off INI setting.
When the enable_post_data_reading INI value is set to Off, or when the HTTP request method is a value apart from POST, the php://input stream wrapper can be read in user-land PHP code to parse the HTTP request data.
curl --request PUT \
--location 'https://example.com/post.php' \
--form 'test="123"'
From a PHP application, the form data of the Curl call above can be read from the php://input stream.
echo file_get_contents('php://input');
----------------------------690112416382325217174003
Content-Disposition: form-data; name="test"
123
----------------------------690112416382325217174003--
New request_parse_body function
PHP 8.4 adds a new function named request_parse_body that exposes the PHP's built-in request parsing functionality for other HTTP request methods.
/**
* Parse and consume php://input and return the values for $_POST
* and $_FILES variables.
*
* @param array<string, int|string>|null $options Overrides for INI values
* @return array<int, array> Array with key 0 being the post data
* (similar to $_POST), and key 1 being the files ($_FILES).
*/
function request_parse_body(?array $options = null): array {}
When called, the request_parse_body function reads the entire contents that are otherwise available on the php://input stream and creates the values that can be used in $_POST and $_FILES variables.
The return value will be an array containing two keys, 0 and 1, containing the parsed values that can be used as $_POST (array key index 0) and $_FILES (index 1). Both array keys will be present at all times — even if there are no request data, and/or files.
It is possible to populate the $_POST and $_FILES values directly from the return values:
[$_POST, $_FILES] = request_parse_body();
Note that the request parsing continues to be bound to the limitations set by the INI directives. For example, if the post_max_size directive (which limits the max size of the requests) is set to 2000 bytes, attempting to call the request_parse_body function with a request larger than that continues to result in an error.
The request parsing options can be overridden with smaller or larger values by passing the $options parameter.
If the request being attempted to parse violates the limits set at the INI directives or the custom options, the request_parse_body function throws a new exception named RequestParseBodyException.
Overriding request parsing options
The $options parameter can be used to pass an array of INI values related to request parsing. These values do not need to be smaller than the global configuration. This gives the advantage to selectively process smaller or larger limits than the limits set at INI files.
For example, to parse a request with a higher or smaller limit to the post_max_size INI directive, call the request_parse_body function with the desired new value:
request_parse_body(['post_max_size' => 1024]);
The $options array only accepts the following overrides:
INI/$option key |
Description |
|---|---|
post_max_size |
Maximum size of POST data that PHP will accept. Its value may be 0 to disable the limit. |
max_input_vars |
How many GET/POST/COOKIE input variables may be accepted |
max_multipart_body_parts |
How many multipart body parts (combined input variable and file uploads) may be accepted. |
max_file_uploads |
Maximum number of files that can be uploaded via a single request |
upload_max_filesize |
Maximum allowed size for uploaded files. |
The values for these keys must be an integer or a quantity string (such as the values allowed by the ini_parse_quantity function.
Passing INI directives apart from the list above lists in a ValueError exception:
request_parse_body(['arbitrary_value' => 42]);
ValueError: Invalid key 'arbitrary_value' in $options argument
Passing non-integer and non-quantity string values results in a PHP warning:
request_parse_body(['post_max_size' => 'arbitrary_value']);
Warning: Invalid quantity "arbitrary_value": no valid leading digits, interpreting as "0" for backwards compatibility
Passing non-string and non-integer values as values to the $options keys results in a ValueError exception:
request_parse_body(['post_max_size' => []]);
ValueError: Invalid array value in $options argument
RequestParseBodyException Exception Class
RequestParseBodyException is a new Exception class declared in the global namespace, extending the Exception class.
class RequestParseBodyException extends Exception {}
RequestParseBodyException exceptions are thrown if the request_parse_body function can not parse the request data. This can occur if the provided request data are invalid, does not send a Content-Type header, or if the request data falls outside the constraint set by INI directives and the optional $options parameter.
The following is a list of RequestParseBodyException exception cases and their cause:
-
RequestParseBodyException: Request does not provide a content typeThe request does not contain aContent-Typeheader. -
RequestParseBodyException: Content-Type ARBITRARY_TYPE is not supportedTheContent-Typeheader contains a value other thanmultipart/form-dataorapplication/x-www-form-urlencoded. -
RequestParseBodyException: Missing boundary in multipart/form-data POST dataThe request does not contain a boundary. Make sure the request is correctly formatted asmultipart/form-dataorapplication/x-www-form-urlencoded. -
RequestParseBodyException: POST Content-Length of ... bytes exceeds the limit of ... bytesThe content length has exceeded thepost_max_sizevalue set at the ($options parameter](#options) or INI directive. -
RequestParseBodyException: Multipart body parts limit exceeded ... To increase the limit change max_multipart_body_parts in php.iniThe request data parts have exceeded themax_multipart_body_partsvalue set at the ($options parameter](#options) or the INI directive. -
RequestParseBodyException: Input variables exceeded ... To increase the limit change max_input_vars in php.ini.The request data parts have exceeded themax_input_varsvalue set at the ($options parameter](#options) or the INI directive. -
RequestParseBodyException: Maximum number of allowable file uploads has been exceededThe number of files being uploaded exceeds themax_file_uploadsvalue set at the ($options parameter](#options) or the INI directive.
request_parse_body caveats
request_parse_body function is designed to be called only one time per request. It provides no way to specify the string to be parsed, and it destructively consumes the php://input. On subsequent calls, the function will return an array with empty data, and the php://input stream will be empty after the first request_parse_body() call.
Non-idempotent behavior of
request_parse_bodyNote that calling therequest_parse_bodyfunction has potential destructive behavior, including it consuming thephp://inputstream and emptying its content.
- Calling
request_parse_bodyfunction consumes thephp://inputstream. Thephp://inputstream will be empty - If the
php://inputstream was read previously (e.g.file_get_contents('php://input'),request_parse_bodyfunction returns an empty result (i.e.[0 => [], 1 => []]). request_parse_bodyfunction does not directly modify$_POSTand$_FILESglobal variables; it is up to the PHP application to overwrite those variables if desired.- Only the first call of
request_parse_bodyfunction returns the parsed data. Subsequent calls return an empty result (i.e.[0 => [], 1 => []]). - Even if the function throws an exception, the
php://inputis still consumed and cleared, and subsequentrequest_parse_bodycalls return an empty result.
Backward Compatibility Impact
This function cannot be polyfilled because it needs to utilize calls to the underlying Server API (SAPI) to obtain the raw data.
This change should not cause any backward compatibility issues unless a PHP application declares its own request_parse_body function.