17、PHP 8.4 新特性 - 新的 request_parse_body 函数
PHP 会自动解析 HTTP POST 请求,以填充 $_POST
和 $_FILES
超全局变量。然而,其他使用如 PUT 和 PATCH 方法的 HTTP 请求不会被自动解析,需要 PHP 应用程序自行解析请求数据。
随着越来越多的 RESTful API 使用诸如 PUT、DELETE 和 PATCH 等 HTTP 方法,一致地解析 HTTP 请求数据变得非常重要。然而,开始自动解析非 POST 请求的数据可能会对现有的 PHP 应用程序造成破坏性变化。
PHP 提供了一个流包装器 php://input
,它包含了请求数据。对于 enctype="multipart/form-data
的 POST 请求,这个流包装器将保持为空,因为数据已经被自动解析并用于填充 $_POST
和 $_FILES
变量。可以通过设置 enable_post_data_reading=Off
的 INI 选项来控制自动的 $_POST
/$_FILES
处理。
当 enable_post_data_reading
INI 值设置为 Off,或者 HTTP 请求方法不是 POST 时,可以在用户空间的 PHP 代码中读取 php://input
流包装器来解析 HTTP 请求数据。
curl --request PUT \
--location '192.168.56.105/php84/post.php' \
--form 'test="123"'
在 PHP 应用程序中,可以从 php://input
流中读取上述 Curl 调用的表单数据。
echo file_get_contents('php://input');
输出如下
--------------------------f8d7c2b189db2b1d
Content-Disposition: form-data; name="test"
123
--------------------------f8d7c2b189db2b1d--
request_parse_body 函数
在 PHP 8.4 中,PHP 引入了一个新的函数 request_parse_body()
,它旨在简化和增强处理 HTTP 请求体的功能,尤其是在处理来自客户端的 POST
数据时。该函数为开发者提供了一个更直接的方式来解析请求体内容。
函数签名
/**
* 解析并处理 php://input,并返回 $_POST 和 $_FILES 变量的值。
*
* @param array<string, int|string>|null $options 用于覆盖 INI 设置的选项
* @return array<int, array> 数组,其中键 0 是 POST 数据
* (类似于 $_POST),键 1 是文件数据 ($_FILES)。
*/
function request_parse_body(?array $options = null): array {}
当调用 request_parse_body
函数时,它会读取 php://input
流中的全部内容,并创建可以用于 $_POST
和 $_FILES
变量的值。
返回值将是一个包含两个键(0 和 1)的数组,其中包含可以分别用作 $_POST
(数组键索引 0)和 $_FILES
(索引 1)的解析值。即使没有请求数据或文件,这两个数组键也始终存在。
可以直接从返回值中填充 $_POST
和 $_FILES
值:
[$_POST, $_FILES] = request_parse_body();
需要注意的是,请求解析仍然受 INI 指令设置的限制。例如,如果 post_max_size
指令(限制请求的最大大小)设置为 2000 字节,尝试使用大于该大小的请求调用 request_parse_body
函数仍会导致错误。
可以通过传递 $options
参数来覆盖请求解析选项,使用更小或更大的值。
如果尝试解析的请求违反了 INI 指令或自定义选项设置的限制,request_parse_body
函数将抛出一个新的异常 RequestParseBodyException
。
覆盖请求解析选项
$options
参数可以用来传递与请求解析相关的 INI 值数组。这些值不需要比全局配置的值小,这提供了选择性处理比 INI 文件中设置的限制更小或更大的限制的优势。
例如,要解析一个具有更高或更低 post_max_size
限制的请求,可以使用所需的新的值调用 request_parse_body
函数:
request_parse_body(['post_max_size' => 1024]);
$options
数组只接受以下覆盖项:
INI/选项键 | 描述 |
---|---|
post_max_size |
PHP 接受的 POST 数据的最大大小。其值可以为 0 以禁用限制。 |
max_input_vars |
可以接受的 GET/POST/COOKIE 输入变量的最大数量。 |
max_multipart_body_parts |
可以接受的多部分主体部分(结合输入变量和文件上传)的最大数量。 |
max_file_uploads |
单个请求可以上传的最大文件数量。 |
upload_max_filesize |
上传文件允许的最大大小。 |
这些键的值必须是整数或数量字符串(如 ini_parse_quantity
函数允许的值)。
传递不在上述列表中的 INI 指令会导致抛出 ValueError
异常:
request_parse_body(['arbitrary_value' => 42]);
ValueError: Invalid key 'arbitrary_value' in $options argument
设置非整数和非数量字符串值会导致 PHP 警告:
request_parse_body(['post_max_size' => 'arbitrary_value']);
Warning: Invalid quantity "arbitrary_value": no valid leading digits, interpreting as "0" for backwards compatibility
传递非字符串和非整数值作为 $options
键的值将导致抛出 ValueError
异常:
request_parse_body(['post_max_size' => []]);
ValueError: Invalid array value in $options argument
使用案例
<?php
declare(strict_types=1);
[$post, $files] = request_parse_body();
print_r($post);
发送 PUT 请求
$ curl -X PUT "http://192.168.56.105/php84/post.php" -H "Content-Type: application/x-www-form-urlencoded" -d "name=John+Doe&age=30"
Array
(
[name] => John Doe
[age] => 30
)
RequestParseBodyException 异常类
RequestParseBodyException
是一个在全局命名空间中声明的新异常类,继承自 Exception
类。
class RequestParseBodyException extends Exception {}
如果 request_parse_body
函数无法解析请求数据,则会抛出 RequestParseBodyException
异常。这可能发生在以下几种情况:
- 提供的请求数据无效。
- 请求未发送
Content-Type
头。 - 请求数据超出由 INI 指令和可选的
$options
参数设置的限制。
以下是 RequestParseBodyException
异常情况及其原因的列表:
-
异常:
RequestParseBodyException: Request does not provide a content type
-
原因:请求中没有包含
Content-Type
头。
-
原因:请求中没有包含
-
异常:
RequestParseBodyException: Content-Type ARBITRARY_TYPE is not supported
-
原因:
Content-Type
头包含的值既不是multipart/form-data
也不是application/x-www-form-urlencoded
。
-
原因:
-
异常:
RequestParseBodyException: Missing boundary in multipart/form-data POST data
-
原因:请求中缺少边界(boundary)。请确保请求正确格式化为
multipart/form-data
或application/x-www-form-urlencoded
。
-
原因:请求中缺少边界(boundary)。请确保请求正确格式化为
-
异常:
RequestParseBodyException: POST Content-Length of ... bytes exceeds the limit of ... bytes
-
原因:内容长度超过了通过
$options
参数或 INI 指令设置的post_max_size
值。
-
原因:内容长度超过了通过
-
异常:
RequestParseBodyException: Multipart body parts limit exceeded ... To increase the limit change max_multipart_body_parts in php.ini
-
原因:请求数据部分的数量超过了通过
$options
参数或 INI 指令设置的max_multipart_body_parts
值。要增加限制,请在php.ini
中修改max_multipart_body_parts
。
-
原因:请求数据部分的数量超过了通过
-
异常:
RequestParseBodyException: Input variables exceeded ... To increase the limit change max_input_vars in php.ini
-
原因:请求数据部分的数量超过了通过
$options
参数或 INI 指令设置的max_input_vars
值。要增加限制,请在php.ini
中修改max_input_vars
。
-
原因:请求数据部分的数量超过了通过
-
异常:
RequestParseBodyException: Maximum number of allowable file uploads has been exceeded
-
原因:上传文件的数量超过了通过
$options
参数或 INI 指令设置的max_file_uploads
值。
-
原因:上传文件的数量超过了通过
request_parse_body注意事项
request_parse_body
函数设计为每个请求只调用一次。它没有提供指定要解析的字符串的方法,并且会破坏性地消耗 php://input
流。在后续调用中,该函数将返回一个包含空数据的数组,并且在第一次调用 request_parse_body()
之后,php://input
流将为空。
request_parse_body
的非幂等行为
请注意,调用 request_parse_body
函数具有潜在的破坏性行为,包括消耗 php://input
流并清空其内容。
-
调用
request_parse_body
函数会消耗php://input
流。php://input
流将在调用后被清空。 -
如果
php://input
流之前已经被读取过(例如使用file_get_contents('php://input')
),request_parse_body
函数将返回一个空的结果(即[0 => [], 1 => []]
)。 -
request_parse_body
函数不会直接修改$_POST
和$_FILES
全局变量;如果需要覆盖这些变量,PHP 应用程序需要自行处理。 -
只有第一次调用
request_parse_body
函数会返回解析后的数据。后续调用将返回空结果(即[0 => [], 1 => []]
)。 -
即使函数抛出异常,
php://input
仍然会被消耗并清空,后续的request_parse_body
调用将返回空结果。
向后兼容性影响
此函数不能通过 polyfill 实现,因为它需要调用底层服务器 API (SAPI) 来获取原始数据。
除非 PHP 应用程序声明了自己的 request_parse_body
函数,否则此更改不应引起任何向后兼容性问题。