skip to content

Superglobals & Raw Input

PHP hands each request to your code as $_GET, $_POST and $_SERVER arrays, plus a php://input stream for raw bodies. Interviewers check where each value comes from and why none is trusted.

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

explore

questions

6

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

level: juniorimportance: must knowfreq 80%

answer

  1. query string versus request body
  2. $_GET is filled for any method
  3. $_POST needs the POST method
  4. form-urlencoded or multipart/form-data only
  5. strings or nested arrays, never trusted

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.

solid answer

~50 s

`$_GET` is built from the **query string** — everything after `?` in the URL — and is filled whenever a query string is present, whatever the method, so `POST /book?ref=mail` fills both arrays. `$_POST` is built from the **request body**, and only when the method is `POST` and the `Content-Type` is `application/x-www-form-urlencoded` or `multipart/form-data`. A JSON body, or a form body sent with `PUT`, leaves `$_POST` empty; the raw bytes are in `php://input` instead. Both arrays map names to strings, or to nested arrays when a field name uses brackets, so a value must be type-checked before use — `$_GET['page']` can be an array. Neither is safer than the other: both come straight from the client. The real difference is how they are used: GET for reads that are safe to repeat and bookmark, POST for changes.

code

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

// POST /rooms/hold?ref=newsletter  with body: room=12&nights=3
$ref    = $_GET['ref'] ?? null;   // "newsletter" - from the query string
$room   = $_POST['room'] ?? null;  // "12" - from the form body, still a string
$nights = $_POST['nights'] ?? null;

if (!is_string($room) || !ctype_digit($room) || !is_string($nights) || !ctype_digit($nights)) {
    http_response_code(422);
    exit('Invalid room or nights');
}

$roomId = (int) $room;
$count  = (int) $nights;

go deeper

for a junior

Recall that $_GET comes from the query string and $_POST from a form-encoded or multipart POST body, and that both are untrusted.

for a middle

Explain exactly when $_POST is populated, why values are strings or arrays, and why a JSON or PUT body leaves $_POST empty.

for a senior

Treat both arrays as untrusted input with type checks at the boundary, and keep state-changing actions on POST so links and prefetches cannot trigger them.

for a principal

Set the convention for how request input enters the codebase, such as one typed request object built from the superglobals, so raw arrays do not spread.

## Where each array comes from PHP builds its request **superglobals** before your script starts. They are ordinary arrays that are visible in every scope — inside functions and methods too — without a `global` statement. | | `$_GET` | `$_POST` | |---|---|---| | Source | The URL's query string, after `?` | The request body | | Filled when | A query string is present, for **any** method | The method is `POST` **and** the body is form-encoded or multipart | | Typical sender | Links, bookmarks, search forms with `method="get"` | Forms with `method="post"` | | Typical use | Filters, pagination, search terms | Creating, changing or deleting data | The name `$_GET` is historical: it means "query-string parameters", not "parameters of a GET request". A `POST` to `/rooms?city=Lisbon` has `$_GET['city']` **and** whatever the body carried in `$_POST`. ## When $_POST stays empty PHP parses the body into `$_POST` only for two content types — `application/x-www-form-urlencoded` and `multipart/form-data` — and only when the method is `POST`. It stays an empty array when: - the client sends JSON, XML or any other content type — read `php://input` instead; - the method is `PUT`, `PATCH` or `DELETE`, even with a form-encoded body; - the body was larger than the configured POST size limit, in which case PHP discards it and raises a warning; - the `enable_post_data_reading` directive is Off, which leaves the body for the script to read itself. An empty `$_POST` is therefore not proof that "nothing was sent". ## Values are strings or arrays Every leaf value in both arrays is a **string** — `?nights=3` gives `"3"`, not `3`. A parameter name with brackets produces a nested array, so a client can turn any expected string into an array by changing the URL: ```php // GET /rooms?city[]=x $city = $_GET['city'] ?? ''; strlen($city); // TypeError: strlen(): Argument #1 ($string) must be of type string, array given ``` Since PHP 8.0 passing an array where a string is expected throws a `TypeError`, so unchecked input can turn into an uncaught error. Check the type before use: 1. Read with a default: `$_GET['page'] ?? '1'`. 2. Confirm the type: `is_string($value)`. 3. Convert or validate explicitly, for example with `filter_var()`, before using the value as a number, an id or a path. ## Neither is trusted A common junior belief is that `$_POST` is safer because it is "hidden". It is not: anyone can send any body with any tool, just as they can edit a URL. Both arrays carry **untrusted input**, and so do `$_COOKIE`, most of `$_SERVER` and `php://input`. Validation and escaping rules apply equally to all of them. ## Choosing GET or POST - Use query parameters for requests that **read** data and can be repeated, cached, shared and bookmarked. - Use a POST body for requests that **change** data, so that a crawler following links or a browser prefetch cannot trigger them. - Do not put secrets such as passwords or tokens in the query string: URLs end up in server access logs, browser history and `Referer` headers. ## Missing keys A parameter the client did not send is simply absent from the array. Reading `$_GET['page']` when there is no `page` raises the warning `Undefined array key "page"` (a notice before PHP 8.0) and yields `null`. Two idioms avoid it: - `$_GET['page'] ?? '1'` supplies a default without any warning; - `isset($_POST['confirm'])` or `array_key_exists('confirm', $_POST)` tests for presence explicitly. Presence and emptiness are different questions: `?q=` sends `q` as the empty string `""`, which is present, while omitting `q` leaves the key out entirely. Search forms, filters and optional flags usually need to tell those two cases apart. ## Writing to superglobals The arrays can be modified like any array, and some frameworks do so when building a request object. Changing `$_GET` affects only the array in the current request; it does not change the URL or any other superglobal. Treat them as read-only input and copy what you need into typed variables.

  • Is $_GET empty for a POST request?
    No. `$_GET` is built from the query string whenever one is present, regardless of the request method. A `POST /book?ref=mail` request has `$_GET['ref']` set and its form fields in `$_POST`. The name reflects the query string, not the method.
  • Why can $_GET['city'] be an array, and what goes wrong if you assume a string?
    A client can write `?city[]=x`, and PHP turns bracketed names into nested arrays. Passing that array to a string function such as `strlen()` or `trim()` throws a `TypeError` in PHP 8. Always check `is_string()` or validate the value before using it as a string.

saying these in an interview costs you the question

  • $_POST is secure because users cannot see or edit the body
  • $_GET is only populated for requests using the GET method
  • $_POST is filled for any body, including JSON
  • Query-string values arrive as integers when they look numeric
  • A PUT request with a form-encoded body fills $_POST
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, 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, 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

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