skip to content

Serving HTTP Requests

How PHP exposes one HTTP exchange: input arrays, form and upload handling, headers and buffered output, cookies and sessions. Interviewers test the mechanics and treat every input as untrusted.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

27

In PHP, how do HTML form field names become $_POST keys, and what do name[] and name[key] produce?

level: juniorimportance: must knowfreq 68%

answer

  1. the name attribute, not the id
  2. values always arrive as strings
  3. name[] appends like $a[]
  4. repeated plain name: last pair wins
  5. multi-select without [] loses options

basics

~20 s

Each submitted control's name attribute becomes a $_POST key holding a string. A name ending in [] appends to a list, name[key] sets that key, and a repeated plain name keeps only its last value, so multi-selects need name[].

solid answer

~40 s

Before the script runs, PHP parses the POST body into `$_POST`, using each control's `name` attribute as the key; the `id` plays no part, and a control with no name or a `disabled` one is never sent. Every value is a **string**, even from a number input, so `ward=4` arrives as `"4"`. Brackets build arrays with the same logic as PHP's own array syntax: `topics[]` appends at the next integer index, `resident[name]` writes the key `name`, and they nest (`answers[q1][]`). A plain name that appears twice simply overwrites itself, which is why `<select multiple name="district">` delivers only the last selected option; name it `district[]`. Because the client controls the names, read defensively: `$_POST['topics'] ?? []`, and check `is_array()` or `is_string()` before use.

code

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

// parse_str() applies the same name-to-key rules that fill $_POST
$body = 'resident[name]=Ana&resident[ward]=4'
      . '&topics[]=parks&topics[]=transit'
      . '&district=north&district=south';
parse_str($body, $post);

var_dump($post['resident']['ward']); // string(1) "4"
var_dump($post['topics']);           // [0 => 'parks', 1 => 'transit']
var_dump($post['district']);         // string(5) "south" - last pair wins

// Reading a checkbox group defensively
$topics = $_POST['topics'] ?? [];
if (!is_array($topics)) {
    $topics = [];
}

go deeper

for a junior

Recall that the name attribute becomes the key, values are strings, and name[] or name[key] turn a field into an array. Know that a multi-select needs brackets.

for a middle

Explain the last-pair-wins rule for repeated plain names, how nested brackets map to nested arrays, and why a key can be absent entirely.

for a senior

Show that request shape is attacker-controlled: an array where a string was expected throws TypeError in PHP 8, so handlers check shape before using values.

for a principal

Argue for one input-mapping layer that turns $_POST into typed objects, so no handler reads raw superglobals and shape checks live in one place.

## From a submitted form to `$_POST` When a browser submits a form with `method="post"` and the default `application/x-www-form-urlencoded` encoding (or `multipart/form-data`), it sends each **successful control** as a `name=value` pair. PHP parses that body during request startup, before the first line of your script executes, and stores each pair in the superglobal array `$_POST`. The key is always the control's **`name` attribute**. The `id`, the `<label>` text and the placeholder are never sent. Which controls count as successful is decided by the browser: - text inputs, textareas, hidden inputs and selected radio buttons send their value, even when it is empty (`""`); - a checkbox or an `<option>` is sent only when it is checked or selected; - a submit button is sent only if it has a name and is the one that was clicked; - a control with no `name`, or with the `disabled` attribute, is not sent at all. ## Plain names: one key, one string A field named `ward` becomes `$_POST['ward']`. Its value is a **string**, whatever the input type: a `type="number"` field holding 4 arrives as `"4"`, not `int(4)`. Converting and validating it is your job. If two pairs share the same plain name, PHP writes the second over the first: the **last pair wins**. The body `district=north&district=south` leaves `$_POST['district'] === 'south'`, with no warning. ## Brackets build arrays PHP reads square brackets in a field name the way it reads them in code: | `name` attribute | Resulting value in `$_POST` | |---|---| | `topics[]` (on several checkboxes) | `$_POST['topics']` is a list: keys `0`, `1`, … in document order | | `resident[name]` and `resident[ward]` | `$_POST['resident']` is `['name' => ..., 'ward' => ...]` | | `score[3]` | integer key `3` (a canonical decimal string becomes an int key) | | `answers[q1][]` | a nested list under `$_POST['answers']['q1']` | | `district` repeated, no brackets | a single string, the last value sent | So `[]` behaves like `$a[] = $v` (append at the next integer index) and `[key]` like `$a['key'] = $v`. You can mix them: `contact[]`, `contact[]`, `contact[email]` yields keys `0`, `1` and `email`. ## Multi-selects and checkbox groups A `<select multiple>` sends one pair per selected option, all under the same name. Named `district`, only the last option survives; named `district[]`, `$_POST['district']` is a list of every selection. A group of checkboxes that should allow several answers works the same way: give them all the same `name` ending in `[]` and distinct `value` attributes. If the user selects nothing, the browser sends no pair at all, so the key is **absent** rather than an empty array. ## A worked example: the council survey Picture a city council survey with a resident block, a group of topic checkboxes and a district multi-select: - `resident[name]` and `resident[ward]` - two text inputs grouped under one key; - `topics[]` on five checkboxes with values `parks`, `transit`, `housing`, `libraries`, `safety`; - `district[]` on a `<select multiple>`; - `comment` on a textarea. A resident who fills in a name and ward, ticks parks and transit, picks two districts and leaves the comment empty produces `$_POST['resident']` with two string keys, `$_POST['topics']` as `['parks', 'transit']`, `$_POST['district']` as a two-element list, and `$_POST['comment']` as `""`. A resident who ticks no topic produces **no** `topics` key at all. The handler therefore reads each part with its own default and shape check rather than assuming the full structure is present. ## Reading the result defensively The client chooses the names, so the shape of `$_POST` is not guaranteed. Three situations to handle: 1. **Missing key** - nothing selected, or a field the form did not render. Use `$_POST['topics'] ?? []` rather than reading the key directly, which in PHP 8 raises an "Undefined array key" warning. 2. **An array where you expect a string** - anyone can post `ward[]=4`. In PHP 8, passing that array to `trim()` throws a `TypeError`, because internal functions now reject wrong-typed arguments instead of returning `null`. 3. **A string where you expect an array** - someone posts `topics=parks`. Check with `is_array()` before looping. Validating the actual values (allowed choices, integer ranges) and escaping them when you print them back are separate steps with their own tools. The name-to-key rules above apply equally to `$_GET` for query strings.

  • In PHP 8, what happens if someone renames the field ward to ward[] and your code calls trim($_POST['ward'])?
    `$_POST['ward']` is now an array, and `trim()` declares a `string` parameter, so PHP 8 throws a `TypeError` ("trim(): Argument #1 ($string) must be of type string, array given"). Uncaught, that is a 500 error. Before PHP 8.0 it emitted a warning and returned `null`. Check the shape with `is_string()` or a filter function before calling string functions on request data.
  • Does a field named score[03] produce the integer key 3 in $_POST?
    No. PHP converts a bracket key to an integer only when it is a canonical decimal integer string, the same rule array literals use. `score[3]` gives int key `3`; `score[03]` and `score[3.0]` keep the string keys `"03"` and `"3.0"`.
  • Do these naming rules apply to $_GET as well?
    Yes. A form with `method="get"` puts the same pairs in the query string, and PHP parses them into `$_GET` with the same rules: brackets build arrays, a repeated plain name keeps the last value, and every value is a string.

saying these in an interview costs you the question

  • PHP uses the input's id attribute as the $_POST key.
  • A number input arrives in $_POST as an int.
  • A multi-select named district delivers every selected option as an array.
  • Repeating a plain field name makes PHP collect the values into an array.
  • Every field rendered in the form always has a key in $_POST.
open as a page

In PHP, what causes the "Cannot modify header information - headers already sent" warning, and how do you fix it?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Headers are sent with the first byte of body output, so header() fails with that warning once anything has been output: an echo, whitespace outside the PHP tags, a BOM or a displayed warning. Send headers before any output.

open as a page

In PHP, why must header('Location: ...') be followed by exit, and which status code does the redirect send?

level: juniorimportance: must knowfreq 66%

basics

~20 s

header('Location: ...') only queues a header; the script keeps running, so code after it still executes and its output is sent. Call exit right after. PHP adds a 302 unless a code is given, so pass 303 or 301 explicitly.

open as a page

In PHP, what does session_start() actually do, and how does $_SESSION data survive from one request to the next?

level: juniorimportance: must knowfreq 80%

basics

~20 s

session_start() takes the session ID from the PHPSESSID cookie or creates a new one, loads that ID's stored data through the save handler into $_SESSION, and PHP writes $_SESSION back to storage when the request ends.

open as a page

In PHP, what is the difference between $_GET and $_POST, and when is each of them populated?

level: juniorimportance: must knowfreq 80%

basics

~10 s

$_GET holds the URL's query-string parameters for any request method; $_POST holds fields parsed from a POST body sent as application/x-www-form-urlencoded or multipart/form-data. Both are client-supplied, so neither is trusted.

open as a page

In PHP, what does a form need to upload a file, what does $_FILES hold, and how do you keep the file?

level: juniorimportance: must knowfreq 70%

basics

~10 s

The form must use method="post" and enctype="multipart/form-data". PHP writes each file to a temporary path and describes it in $_FILES; move_uploaded_file() moves it somewhere permanent, otherwise PHP deletes it when the request ends.

open as a page

In a PHP form handler, what problem does Post/Redirect/Get solve, and how do you redisplay validation errors with it?

level: middleimportance: must knowfreq 62%

basics

~20 s

Post/Redirect/Get stops a refresh or Back from resubmitting a POST: after processing, the handler answers with a 303 redirect to a GET page. Errors are shown by re-rendering the form directly, or by flashing old input and errors in the session.

open as a page

In PHP, what do session_regenerate_id() and session.use_strict_mode each protect against, and what are their defaults?

level: middleimportance: must knowfreq 62%

basics

~10 s

Both defend against session fixation. session_regenerate_id() swaps the ID after a privilege change such as login, keeping the data; session.use_strict_mode, off by default, makes session_start() refuse IDs the server never issued.

open as a page

Why is $_POST empty when a payment provider posts a JSON webhook to a PHP endpoint, and how do you read that body?

level: middleimportance: must knowfreq 58%

basics

~20 s

PHP fills $_POST only for form-encoded and multipart bodies, so a JSON body is left unparsed. Read the raw bytes with file_get_contents('php://input'), verify the provider's signature on those exact bytes, then decode them with json_decode().

open as a page

In PHP, why shouldn't an upload handler trust $_FILES['cv']['type'] or the file's extension, and how do you check the real type?

level: middleimportance: must knowfreq 55%

basics

~20 s

Both type and the name's extension come from the client and can say anything. Detect the type from the file's bytes with finfo and FILEINFO_MIME_TYPE on tmp_name, compare it with an allowlist, and derive the stored extension from the detected type.

open as a page

In PHP, why is an unchecked checkbox missing from $_POST, and how do you read it reliably as a boolean?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Browsers submit a checkbox only when it is checked, sending its value ("on" when none is set), so an unchecked box leaves no key. Read it with isset() or a ?? default, or put a same-named hidden field before it.

open as a page

In PHP, how do ob_start(), ob_get_clean() and ob_end_flush() work, and what does the output_buffering ini directive change?

level: middleimportance: should knowfreq 45%

basics

~20 s

ob_start() pushes a buffer that captures output; ob_get_clean() returns its contents and removes it; ob_end_flush() sends them to the next level and removes it. The output_buffering directive starts one buffer for every request: 4096 bytes in the shipped php.ini files.

open as a page

In PHP, what does header()'s $replace argument do, and how do header_remove() and http_response_code() change a pending response?

level: middleimportance: should knowfreq 40%

basics

~20 s

header() replaces an earlier header with the same name unless $replace is false, which adds another line. header_remove() deletes one header by name, or all with no argument. http_response_code() sets the status and returns the previous one.

open as a page

In PHP, what does $_REQUEST contain, how does request_order control it, and why do most codebases avoid it?

level: middleimportance: should knowfreq 38%

basics

~20 s

$_REQUEST merges $_GET, $_POST and possibly $_COOKIE in the order request_order lists them. The shipped php.ini files set "GP", so POST overrides GET and cookies are left out. Codebases avoid it because it hides where a value came from.

open as a page

In PHP, how do HTTP request headers appear in $_SERVER, and why is Content-Type read from CONTENT_TYPE instead?

level: middleimportance: should knowfreq 46%

basics

~10 s

Each request header becomes $SERVER['HTTP' . NAME], uppercased with hyphens as underscores, so Accept-Language is HTTP_ACCEPT_LANGUAGE. Content-Type and Content-Length follow the CGI convention instead, arriving as CONTENT_TYPE and CONTENT_LENGTH without the prefix.

open as a page

In PHP, what shape does $_FILES take for <input type="file" name="docs[]" multiple>, and how do you loop over the files?

level: middleimportance: should knowfreq 38%

basics

~10 s

PHP groups by attribute, not by file: $_FILES['docs']['name'][0], $_FILES['docs']['tmp_name'][0] and so on. Loop over the keys of $_FILES['docs']['error'] and read each attribute at that index, or normalise into one array per file first.

open as a page

In PHP, which UPLOAD_ERR_* codes can $_FILES report, and what does each tell an upload handler to do?

level: middleimportance: should knowfreq 45%

basics

~20 s

The error key holds UPLOAD_ERR_OK (0) on success, or a code for too large (INI_SIZE 1, FORM_SIZE 2), interrupted (PARTIAL 3), no file (NO_FILE 4) or a server fault (NO_TMP_DIR 6, CANT_WRITE 7, EXTENSION 8). Only 0 has a usable temp file.

open as a page

A PHP admin form with 60 rows of 20 fields saves only the first rows and shows no error; how do you diagnose and fix it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

60 rows of 20 fields is 1,200 pairs, above max_input_vars (default 1000). PHP keeps the pairs up to the limit, drops the rest, and logs an E_WARNING. Raise it per directory or pool, or send fewer fields.

open as a page

A PHP admin panel's CSV export of 500,000 rows exhausts memory or arrives only at the end; how do you stream it as a download?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Set the download headers, close any open output buffers, then write each row to php://output as it is fetched and call flush() periodically. Validate everything before the first row, because once output starts the status and headers can no longer change.

open as a page

A PHP shopping-basket page fires four AJAX requests at once, yet they finish one after another instead of in parallel; why, and how do you fix it?

level: seniorimportance: should knowfreq 50%

basics

~20 s

The default files handler holds an exclusive lock on the session file from session_start() until the session is written, so requests sharing one session ID queue. Release it early with session_write_close(), or use read_and_close where nothing is written.

open as a page

In PHP, which $_SERVER entries can a client control, and which can you rely on — REMOTE_ADDR, HTTP_HOST, SERVER_NAME, PHP_SELF?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Every HTTP_* entry, REQUEST_URI, QUERY_STRING and PHP_SELF carry client input. SERVER_NAME can reflect the client's Host header unless the server pins it. REMOTE_ADDR is the connecting peer's address — reliable, but behind a proxy it is the proxy's.

open as a page

A PHP job form with upload_max_filesize = 10M accepts a 5 MB CV, but a 9 MB CV arrives with $_POST and $_FILES both empty; why?

level: seniorimportance: should knowfreq 42%

basics

~20 s

post_max_size (default 8M) caps the whole request body, and a 9 MB CV plus fields exceeds it. PHP then logs a warning and parses nothing, so $_POST and $_FILES are empty. Set post_max_size above upload_max_filesize times the files per request.

open as a page

In PHP, what happens to form field names with dots or spaces, and to names like rows[][name] and rows[][email]?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

PHP turns dots and spaces in the top-level part of a field name into underscores, so user.email arrives as $_POST['user_email']. Every [] appends a new element, so rows[][name] and rows[][email] land in two different rows; use explicit indexes.

open as a page

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%

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.

open as a page

In PHP, how do you implement and register a custom session save handler with SessionHandlerInterface, and what must it handle itself?

level: seniorimportance: nice to knowfreq 25%

basics

~10 s

Implement SessionHandlerInterface's open, close, read, write, destroy and gc, register the object with session_set_save_handler($handler, true) before session_start(), and handle locking, ID validation and timestamp refreshes yourself.

open as a page