PHP 8.4: Новая функция request_parse_body
Как вы знаете PHP автоматически анализирует HTTP-запросы POST для заполнения глобальных переменных $_POST
и $_FILES
. Однако другие HTTP-запросы с такими методами, как PUT
или PATCH уже
не анализируются автоматически, и работа с обработкой данных с данными типами запроса ложится на плечи разработчика/приложения.
RFC1867 определяет тип контента multipart/form-data
. Этот тип контента используется в основном для отправки HTTP-форм, содержащих файлы. PHP изначально поддерживает анализ этого типа контента, но только для запросов POST. В частности, если запрос имеет метод POST и тип содержимого multipart/form-data, тело запроса немедленно используется перед запуском PHP-скрипта и заполняется в суперглобальные переменные $_POST и $_FILES. Эта функция запускается автоматически и не предоставляется непосредственно пользователю.
Учитывая популярность REST API
, которые все чаще используют HTTP методы, отличные от POST - такие как PUT
, DELETE
и PATCH
, где использование multipart/form-data полностью допустимо, но не обрабатывается PHP автоматически. Это требует ручного анализа тела запроса нетривиального формата. Обработка больших объемов данных в пользовательской среде также может быть не оптимальной с точки зрения производительности.
Например в Symfony эта часть отвечает за анализ таких данных, в том же Phalcon мне пришлось в свое время тоже добавить эту возможность и так в любом мало-мальски используемом фреймворке.
UPD: Вот отличный пример реализации от Кирилла Несмеянова, который при свободном времени можно оформить в пакет)
Если вкратце, PHP предоставляет обёртку потока php://input
, содержащую данные запроса. Для POST
запросов с enctype="multipart/form-data"
эта обёртка потока остается пустой, поскольку она автоматически анализируется и используется для заполнения переменных $_POST и
$_FILES
. Автоматической обработкой $_POST и
$_FILES
можно управлять с помощью директивы enable_post_data_reading=Off в INI
.
Если значение enable_post_data_reading
установлено как Off
, или если метод HTTP-запроса имеет значение, отличное от POST
, php://input
обёртку потока можно прочитать в PHP-коде приложения для анализа данных HTTP-запроса:
curl --request PUT --location 'https://somehost.com/somepath' --form 'foo="bar"'
В PHP-приложении данные формы приведенного выше вызова Curl можно прочитать из потока php://input
:
echo file_get_contents('php://input');
----------------------------690112416382325217175117
Content-Disposition: form-data; name="foo"
bar
----------------------------690112416382325217175117--
Итак, в PHP 8.4 была добавлена новая функция request_parse_body, которая предоставляет встроенную в PHP функциональность анализа запросов для других методов HTTP-запросов, отличных от POST:
/**
* 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 {}
// Например, это запрос PUT
var_dump($_POST); // []
var_dump($_FILES); // []
[$_POST, $_FILES] = request_parse_body();
var_dump($_POST); // уже есть данные [...]
var_dump($_FILES); // уже есть данные [...]
При вызове функция request_parse_body считывает все содержимое, которое доступно в потоке php://input и создает значения, которые можно использовать в переменных $_POST и $_FILES.
Возвращаемым значением будет массив, c двумя ключами, 0 и 1, содержащий проанализированные значения, которые можно использовать как $_POST(индекс ключа массива 0) и $_FILES(индекс 1). Оба ключа массива будут присутствовать всегда - даже если нет данных запроса и/или файлов.
Можно заполнить значения $_POST и $_FILES непосредственно из возвращаемых значений:
[$_POST, $_FILES] = request_parse_body();
Обратите внимание, что синтаксический анализ запроса по-прежнему имеет ограничения, установленные директивами INI. Например, если директива post_max_size (которая ограничивает максимальный размер запросов) установлена в 2 мегабайта, попытка вызвать функцию request_parse_body с запросом большего размера по-прежнему будет приводить к ошибке.
Параметры анализа запроса можно переопределить на меньшие или большие значения, передав параметр $options.
Если запрос, который пытаются проанализировать, нарушает ограничения, установленные в директивах INI или пользовательских параметрах, функция request_parse_body генерирует новое исключение RequestParseBodyException
.
Переопределение параметров анализа запроса
Параметр $options можно использовать для передачи массива значений INI, связанных с анализом запроса. Эти значения не обязательно должны быть меньше, чем глобальная конфигурация. Это дает преимущество выборочной обработки меньших или больших значений, чем ограничения, установленные в файлах INI.
Например, чтобы проанализировать запрос с большим или меньшим пределом директивы post_max_size INI, можно вызвать функцию request_parse_body с нужным значением:
request_parse_body(['post_max_size' => 1024]);
Массив $options принимает только следующие переопределения:
INI/ | Описание |
---|---|
post_max_size | Максимальный размер данных POST, которые принимает PHP. Его значение может быть 0 - чтобы отключить ограничение. |
max_input_vars | Сколько входных переменных GET/POST/COOKIE можно принять |
max_multipart_body_parts | Сколько составных частей тела (комбинированная входная переменная и загрузка файлов) может быть принято. |
max_file_uploads | Максимальное количество файлов, которые можно загрузить по одному запросу. |
upload_max_filesize | Максимально допустимый размер загружаемых файлов. |
Предоставление неверных ключей или значений приведет к возникновению ошибки ValueError
.
Класс исключения RequestParseBodyException
RequestParseBodyException - это новый класс Exception, объявленный в глобальном пространстве имен, расширяющий класс Exception.
class RequestParseBodyException extends Exception {}
RequestParseBodyException
исключения выдаются, если функция request_parse_body
не может проанализировать данные запроса. Это может произойти, если предоставленные данные запроса недействительны, не отправляют заголовок 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
Запрос не содержит границы. Убедитесь, что запрос правильно отформатирован как
multipart/form-data
илиapplication/x-www-form-urlencoded
.RequestParseBodyException: POST Content-Length of ... bytes exceeds the limit of ... bytes
Длина содержимого превысила значение post_max_size, установленное в $options или INI.
RequestParseBodyException: Multipart body parts limit exceeded ... To increase the limit change max_multipart_body_parts in php.ini
Части данных запроса превысили значение max_multipart_body_parts, установленное в параметре $options или INI.
RequestParseBodyException: Input variables exceeded ... To increase the limit change max_input_vars in php.ini.
Части данных запроса превысили значение max_input_vars, установленное в $options или INI.
RequestParseBodyException: Maximum number of allowable file uploads has been exceeded
Количество загружаемых файлов превышает значение max_file_uploads, установленное в $options или INI.
Предостережения request_parse_body
Функция request_parse_body предназначена для вызова только один раз за запрос и разрушительно воздействует на php://input. При последующих вызовах функция вернет массив с пустыми данными, а поток php://input будет пуст после первого вызова request_parse_body().
Не идемпотентное поведение 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 вызовы возвращают пустой результат.
Итак, на выходе мы имеем нативную функцию, которая позволит избавиться от некоторой логики в фреймворках или пользовательских приложений, будет иметь более высокую скорость обработки и безопасность.
Давайте дружно похлопаем и поблагодарим Илью Товило за столь замечательный функционал!
Что думаешь?