skip to content

Form Submissions

HTML form fields arrive in $_POST by name, with name[] building arrays and unchecked checkboxes sent not at all. Interviewers probe the edge cases and Post/Redirect/Get against resubmits.

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

explore

questions

5

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 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, 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

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

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