skip to content

In PHP 8.4 and later, what does request_parse_body() do, and why do PUT and PATCH form submissions need it?

level: middleimportance: nice to knowfreq 18%

answer

  1. $_POST and $_FILES: POST only
  2. added in PHP 8.4
  3. returns a [$post, $files] pair
  4. options override post_max_size and friends
  5. the body can be consumed only once

basics

~20 s

PHP fills $_POST and $_FILES only for POST requests. request_parse_body(), added in PHP 8.4, runs the same form and multipart parser on demand — for PUT, PATCH or DELETE — and returns a [$post, $files] pair.

solid answer

~40 s

PHP parses `application/x-www-form-urlencoded` and `multipart/form-data` bodies into `$_POST` and `$_FILES` automatically, but only when the method is `POST`. A REST-style `PUT /rooms/12` or `PATCH` carrying a multipart form got nothing, and before PHP 8.4 you had to parse the multipart body from `php://input` yourself. `request_parse_body(?array $options = null): array`, added in 8.4, reads the body and parses it according to its `Content-Type`, returning an array whose index `0` is the `$_POST` equivalent and index `1` the `$_FILES` equivalent. The `$options` array overrides the ini limits for this parse: `max_file_uploads`, `max_input_vars`, `max_multipart_body_parts`, `post_max_size` and `upload_max_filesize`. A malformed body throws `RequestParseBodyException`, and an invalid option a `ValueError`. The body can be consumed only once: the function does not buffer it into `php://input`, and returns empty data if the body was already read.

code

php · 18 lines
php
<?php
declare(strict_types=1);

// PATCH /rooms/12  Content-Type: multipart/form-data; boundary=...
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'PATCH') {
    try {
        [$fields, $files] = request_parse_body([
            'upload_max_filesize' => '8M',
            'max_file_uploads'    => 4,
        ]);
    } catch (RequestParseBodyException $e) {
        http_response_code(400);
        exit('Malformed form body');
    }

    $title  = $fields['title'] ?? null;   // same shape as $_POST
    $photos = $files['photos'] ?? null;   // same shape as $_FILES
}

go deeper

for a junior

Recall that $_POST and $_FILES are filled only for POST, and that PHP 8.4 added request_parse_body() for other methods.

for a middle

Explain the returned [$post, $files] pair, the option keys that override ini limits, the exceptions it throws, and why the body can be read only once.

for a senior

Use request_parse_body() for multipart PUT and PATCH endpoints with per-endpoint limits, and make sure nothing reads php://input before it.

for a principal

Decide whether APIs accept multipart for non-POST methods at all, and how body-reading responsibilities are split between middleware and handlers.

## The gap it fills PHP's automatic body parsing has always been tied to the `POST` method. Before a script starts, PHP parses the body into `$_POST` (and uploaded files into `$_FILES`) only when: - `enable_post_data_reading` is On; - the method is `POST`; - the `Content-Type` is `application/x-www-form-urlencoded` or `multipart/form-data`. REST-style APIs and HTML-over-the-wire front ends often send forms with `PUT`, `PATCH` or `DELETE`. For those, `$_POST` and `$_FILES` stay empty. Before PHP 8.4 the only options were to parse the raw multipart body from `php://input` in userland — slow, memory-hungry and easy to get wrong — or to pull in a library that did so. ## What request_parse_body() does `request_parse_body(?array $options = null): array`, added in **PHP 8.4**, runs PHP's own body parser on demand: 1. It reads the request body. 2. It parses it according to the request's `Content-Type`, supporting the same two types as `$_POST`: form-urlencoded and multipart. 3. It returns a two-element array: index `0` holds what `$_POST` would contain, index `1` what `$_FILES` would contain. Destructuring makes the call read naturally: `[$fields, $files] = request_parse_body();`. Uploaded files returned this way have the same shape as `$_FILES` entries. ## Options and limits The optional `$options` array overrides, for this call only, the php.ini directives that limit body parsing: | Option key | Limits | |---|---| | `post_max_size` | The total body size | | `upload_max_filesize` | Each uploaded file | | `max_file_uploads` | The number of files | | `max_input_vars` | The number of form fields | | `max_multipart_body_parts` | The number of multipart parts | This lets one endpoint accept larger uploads without raising the limits for the whole application. ## Errors - A body that is invalid for its `Content-Type` throws **`RequestParseBodyException`**, a subclass of `Exception`. - An unknown option key, or an invalid value for a known key, throws **`ValueError`**. Both are exceptions, so an endpoint can map a malformed body to a 400 response instead of failing silently. ## The body is consumed once The request body is a stream that PHP reads from the client once: - `request_parse_body()` consumes the body **without** buffering it into `php://input`, so reading `php://input` afterwards yields nothing useful; - if the body was already read — for example through `php://input` — `request_parse_body()` returns empty data. Choose one way to read each request's body. A framework that reads `php://input` early for logging will break a later `request_parse_body()` call. ## Using it for POST too With `enable_post_data_reading` set to Off, PHP does not parse even `POST` bodies. The body then remains available, and `request_parse_body()` can parse it when — and only if — the endpoint actually needs the fields. That suits proxies and endpoints that stream large bodies elsewhere. ## Before PHP 8.4 Applications that needed multipart `PUT` or `PATCH` support before 8.4 had three options, all worse: - read `php://input` and parse the multipart boundaries in PHP code, which is slow and duplicates what the engine already does in C; - have clients send `POST` with a method-override field such as `_method=PATCH`, a convention many frameworks still support; - accept only JSON for those methods and keep file uploads on `POST` endpoints. ## Interplay with frameworks Frameworks that build their own request objects may already read the body during bootstrap. Because the body can be consumed only once, calling `request_parse_body()` inside a controller after the framework has read `php://input` returns empty data. Use it in plain PHP entry points, or in the one place where your framework creates the request object, not in individual handlers. ## Version notes - Available from **PHP 8.4**; code that must also run on 8.3 or earlier needs a fallback parser. - It does not decode JSON. A JSON body is still read with `file_get_contents('php://input')` and `json_decode()`.

  • What does request_parse_body() return if middleware already read php://input?
    Empty data. The request body is consumed once, and `request_parse_body()` has nothing left to parse when something else read the body first. The reverse also holds: after `request_parse_body()`, `php://input` does not hold the body, because the function does not buffer it there.
  • Does request_parse_body() decode a JSON body?
    No. It supports the same two content types as automatic POST parsing, `application/x-www-form-urlencoded` and `multipart/form-data`. JSON bodies are still read with `file_get_contents('php://input')` and decoded with `json_decode()`.

saying these in an interview costs you the question

  • PHP 8 fills $_POST for PUT requests with form bodies
  • request_parse_body() decodes JSON bodies too
  • You can call request_parse_body() after reading php://input
  • request_parse_body() returns an object with post and files properties
  • A malformed body makes it return false