Handling Forms and Requests in PHP
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
Handling user input is the riskiest part of web development: anything arriving in a request is untrusted. This chapter covers receiving, validating and re-emitting form data safely.
Superglobals
PHP puts request data into superglobal arrays, available inside functions without any declaration:
| Variable | Contents |
|---|---|
$_GET |
Query string parameters (?id=1) |
$_POST |
Request body of a method="post" form |
$_SERVER |
Request and server info: REQUEST_METHOD, REQUEST_URI |
$_FILES |
Uploaded file metadata |
$_COOKIE |
Cookies sent by the browser |
$_SESSION |
Session data (after session_start()) |
Reading a missing key warns, so always fall back:
$id = $_GET["id"] ?? null;
GET versus POST
- GET for queries, filters, pagination — operations that do not change server state. Parameters live in the URL, so the page can be bookmarked and shared.
- POST for submissions that create or modify data. Parameters travel in the body, not the URL.
Check the method like this:
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// handle submission
}
Do not test whether $_POST is empty — a POST with no fields defeats it. REQUEST_METHOD is the accurate signal.
A minimal form handler
Save as form.php, run php -S localhost:8000, and open http://localhost:8000/form.php:
<?php
declare(strict_types=1);
$errors = [];
$values = ["name" => "", "email" => ""];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$values["name"] = trim($_POST["name"] ?? "");
$values["email"] = trim($_POST["email"] ?? "");
if (mb_strlen($values["name"], "UTF-8") < 2) {
$errors["name"] = "Please enter at least 2 characters";
}
if (!filter_var($values["email"], FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "That email address is not valid";
}
if ($errors === []) {
// validated: persist, then redirect
header("Location: /thanks.php", true, 303);
exit;
}
}
?>
<!doctype html>
<meta charset="utf-8">
<title>Sign up</title>
<?php if ($errors !== []): ?>
<p>Please fix the problems below and resubmit.</p>
<?php endif; ?>
<form method="post" action="/form.php">
<label>Name
<input name="name" value="<?= htmlspecialchars($values["name"], ENT_QUOTES, "UTF-8") ?>">
</label>
<?php if (isset($errors["name"])): ?>
<em><?= htmlspecialchars($errors["name"], ENT_QUOTES, "UTF-8") ?></em>
<?php endif; ?>
<label>Email
<input name="email" type="email" value="<?= htmlspecialchars($values["email"], ENT_QUOTES, "UTF-8") ?>">
</label>
<?php if (isset($errors["email"])): ?>
<em><?= htmlspecialchars($errors["email"], ENT_QUOTES, "UTF-8") ?></em>
<?php endif; ?>
<button type="submit">Submit</button>
</form>
The three decisions that matter:
- Redirect after success (Post/Redirect/Get) so refreshing the browser cannot resubmit. Use status
303and alwaysexitafterheader(). - On failure, re-render the form instead of redirecting, refilling what the user typed.
- Escape every value printed into HTML.
Escaping output: preventing XSS
echo htmlspecialchars($userInput, ENT_QUOTES, "UTF-8");
htmlspecialchars turns <, >, &, " and ' into entities so the browser treats them as text. Pass all three arguments:
ENT_QUOTES— escapes both quote types; escaping only double quotes still leaves single-quoted attributes exploitable."UTF-8"— states the charset instead of letting PHP guess.
Without it:
// the user typed: <script>alert(1)</script>
echo $values["name"]; // the browser executes it
Escape on output, not on storage. Keep the original in the database and escape when rendering to HTML, so the same data stays clean for JSON, CSV or plain text.
Validating input with filter_var
$email = filter_var($_POST["email"] ?? null, FILTER_VALIDATE_EMAIL);
$age = filter_var($_POST["age"] ?? null, FILTER_VALIDATE_INT, [
"options" => ["min_range" => 0, "max_range" => 150],
]);
$url = filter_var($_POST["site"] ?? null, FILTER_VALIDATE_URL);
Failure returns false, and 0 is a valid result, so compare with === false:
if ($age === false) {
$errors["age"] = "Age must be an integer of 150 or less";
}
You can also read straight from a superglobal:
$page = filter_input(INPUT_GET, "page", FILTER_VALIDATE_INT, [
"options" => ["default" => 1, "min_range" => 1],
]);
filter_input has the advantage of not warning when the key is absent.
Common mistakes
1. Echoing $_POST["name"] directly
That is an XSS hole. Wrap it in htmlspecialchars.
2. Using if ($_POST) to detect a POST request
An empty submission breaks it. Read $_SERVER["REQUEST_METHOD"].
3. Not redirecting after a successful submission
Refreshing resubmits the form.
4. Forgetting exit after header()
The rest of the script still runs and may leak output or override the redirect.
5. Comparing filter_var with == false
0 and "0" are treated as failure. Use === false.
6. Trusting client-side validation
Browser validation is bypassable; server-side checks are the actual boundary.
Summary
$_GETqueries,$_POSTsubmissions; branch onREQUEST_METHOD.- Read request values with
??, never raw. - Escape with
htmlspecialchars($v, ENT_QUOTES, "UTF-8")at render time, not at save time. - Validate with
filter_varand compare failures against=== false. - On success:
303redirect plusexit.
Next: reading and writing files, from one-liners to streaming large logs.