PHP 8.4: Новая функция request_parse_body

Источник: «PHP 8.4: New request_parse_body function»
В PHP 8.4 добавлена новая функция request_parse_body, раскрывающая встроенную в PHP функциональность парсинга HTTP-запросов отличных от POST.

PHP автоматически разбирает HTTP POST-запросы, чтобы заполнить суперглобальные переменные $_POST и $_FILES. Однако другие HTTP-запросы с такими методами, как PUT и PATCH, не разбираются автоматически, и разбор данных запроса остаётся за PHP-приложением.

С популярностью REST API, в которых всё чаще используются такие HTTP-методы, как PUT, DELETE и PATCH, разбор данных HTTP-запросов становится всё более важным. Однако автоматический разбор данных HTTP-запроса для запросов, не относящихся к POST, может стать серьёзным изменением для существующих PHP-приложений.

PHP предоставляет обёртку потока по адресу php://input, содержащую данные запроса. Для POST-запросов с enctype="multipart/form-data" эта потоковая обёртка остаётся пустой, потому что она автоматически парсится и используется для заполнения переменных $_POST и $_FILES. Автоматической обработкой $_POST/$_FILES можно управлять с помощью INI-настройки enable_post_data_reading=Off.

Когда INI-значение enable_post_data_reading установлено в Off, или когда метод HTTP-запроса имеет значение, отличное от POST, потоковая обёртка php://input может быть прочитана в пользовательском PHP-коде для разбора данных HTTP-запроса.

curl --request PUT \
--location 'https://example.com/post.php' \
--form 'test="123"'

Из PHP-приложения данные формы, полученные в приведённом выше вызове Curl, могут быть прочитаны из потока php://input.

echo file_get_contents('php://input');
----------------------------690112416382325217174003
Content-Disposition: form-data; name="test"
123
----------------------------690112416382325217174003--

Новая функция request_parse_body

В PHP 8.4 добавлена новая функция request_parse_body, раскрывающая встроенную в PHP функциональность парсинга запросов для других методов HTTP-запросов.

/**
* 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 {}

При вызове функция 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-файлах.

Например, чтобы разобрать запрос с большим или меньшим ограничением INI-директивы post_max_size, вызовите функцию request_parse_body с желаемым новым значением:

request_parse_body(['post_max_size' => 1024]);

Массив $options принимает только следующие переопределения:

INI/$option ключОписание
post_max_sizeМаксимальный размер POST-данных, которые будет принимать PHP. Его значение может быть равно 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

Класс исключений RequestParseBodyException

RequestParseBodyException — новый класс Exception, объявленный в глобальном пространстве имён и расширяющий класс Exception.

class RequestParseBodyException extends Exception {}

Исключения RequestParseBodyException выбрасываются, если функция request_parse_body не может разобрать данные запроса. Это может произойти, если предоставленные данные запроса некорректны, не отправлен заголовок Content-Type, или если данные запроса выходят за рамки ограничений, установленных директивами INI и дополнительным параметром $options.

Ниже приведён список исключений RequestParseBodyException и их причины:

Предостережения по request_parse_body

Функция request_parse_body предназначена для того, чтобы вызываться только один раз за запрос. Она не предоставляет возможности указать строку для разбора и деструктуризации php://input. При последующих вызовах функция будет возвращать массив с пустыми данными, а поток php://input будет пуст после первого вызова request_parse_body().

Не идемпотентное поведение request_parse_body. Обратите внимание, что вызов функции request_parse_body имеет потенциально деструктивное поведение, в том числе он поглощает поток php://input и очищает его содержимое.

Влияние на обратную совместимость

Эта функция не может быть полифункциональной, поскольку для получения исходных данных ей необходимо использовать вызовы базового Server API (SAPI).

Это изменение не должно вызвать проблем с обратной совместимостью, если только PHP-приложение не объявит свою собственную функцию request_parse_body.


Дополнительные материалы

Предыдущая Статья

PHP 8.4: Расширения OCI8 и PDO-OCI перенесены из PHP Core в PECL

Следующая Статья

PHP 8.4: Curl: Минимальная требуемая версия libcurl повышена до 7.61.0