PHP Form Handling
What Is Form Handling? In simple terms: Form handling is everything that happens between a user clicking "Submit" and seeing the result: the browser sends the entered data to the server, PHP reads it,
What Is Form Handling?
In simple terms: Form handling is everything that happens between a user clicking "Submit" and seeing the result: the browser sends the entered data to the server, PHP reads it, checks it, does something useful with it (saves it, sends an email, searches), and responds with a page.
Technically: An HTML <form> makes the browser send an HTTP request — GET with the data in the URL, or POST with the data in the request body. PHP parses the request into the superglobals $_GET, $_POST, and $_FILES, and your script processes those values and generates a response.
Why It Matters
Forms are how users interact with web applications: logging in, registering, searching, placing orders, sending messages, uploading files. Handling them correctly means:
- accepting valid input and giving clear feedback on invalid input,
- protecting the application from malicious input,
- never losing what the user typed, and
- never processing the same submission twice by accident.
This lesson explains the complete flow. The following lessons cover each part in depth: PHP Form Validation, PHP Form Required, PHP Form URL/E-mail, Form Input Sanitizing, Escaping Output (htmlspecialchars), Checkbox, Radio and Select Inputs, Sticky Forms, and PHP Form Complete.
How a Form Submission Works
1. Browser shows the form (GET /contact.php)
2. User fills in fields and clicks Submit
3. Browser sends: POST /contact.php body: name=Asha&email=asha%40example.com
4. PHP fills $_POST = ['name' => 'Asha', 'email' => 'asha@example.com']
5. Your script validates and processes the data
6. Response: redirect to a success page, or show the form again with errors
The Form Element
<form action="/welcome.php" method="post">
<label for="name">Name</label>
<input type="text" id="name" name="name">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Submit</button>
</form>
| Attribute | Meaning |
|---|---|
action | URL that receives the data. Empty (action="") or omitted means the current URL. |
method | get or post (default: get) |
enctype | How the data is encoded; multipart/form-data is required for file uploads |
name (on inputs) | The key under which the value is sent — fields without name are not sent |
id + <label for> | Connects labels to inputs for accessibility; not sent to the server |
A Simple Form Handler
welcome.php
<?php
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
?>
<p>Welcome, <?= htmlspecialchars($name) ?>!</p>
<p>Your email address is: <?= htmlspecialchars($email) ?></p>
Output After Submitting "Asha" and "asha@example.com"
<p>Welcome, Asha!</p>
<p>Your email address is: asha@example.com</p>
What This Example Already Does Right
?? ''provides defaults if a field is missing.trim()removes accidental spaces.htmlspecialchars()escapes the values before printing. If someone submits<script>...</script>as their name, it is displayed as harmless text instead of being executed.
What It Still Lacks
This minimal handler accepts anything: an empty name, an invalid email, or a request sent without the form. Real handlers must validate input, report errors, and protect against attacks — covered below.
GET Forms for Searching and Filtering
Forms that only retrieve information should use GET, so the resulting page can be bookmarked and shared:
<?php
// search.php
$query = is_string($_GET['q'] ?? null) ? trim($_GET['q']) : '';
?>
<form action="/search.php" method="get">
<input type="search" name="q" value="<?= htmlspecialchars($query) ?>">
<button type="submit">Search</button>
</form>
<?php if ($query !== ''): ?>
<p>Results for "<?= htmlspecialchars($query) ?>"</p>
<?php endif; ?>
After searching for php arrays, the URL becomes /search.php?q=php+arrays. The search box is filled with the current query, so the user can refine it. See \$_GET.
| Use GET When | Use POST When |
|---|---|
| Searching, filtering, sorting, paginating | Creating, updating, or deleting data |
| The result should be bookmarkable | Submitting passwords or personal data |
| Repeating the request is harmless | Uploading files or large amounts of text |
The Self-Processing Form Pattern
A common structure keeps the form and its processing in the same file:
contact.php
├── PHP at the top: if the request is POST → read, validate, process
└── HTML below: display the form (with errors and previous values)
<?php
$errors = [];
$values = ['name' => ''];
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$values['name'] = is_string($_POST['name'] ?? null) ? trim($_POST['name']) : '';
if ($values['name'] === '') {
$errors['name'] = 'Please enter your name.';
}
if ($errors === []) {
// process the data, then redirect (see Post/Redirect/Get below)
header('Location: /thank-you.php', true, 303);
exit;
}
}
?>
<form method="post" action="">
<input name="name" value="<?= htmlspecialchars($values['name']) ?>">
<?php if (isset($errors['name'])): ?>
<span class="error"><?= htmlspecialchars($errors['name']) ?></span>
<?php endif; ?>
<button type="submit">Send</button>
</form>
Why This Structure Works
- First visit (GET): the processing block is skipped; the empty form is shown.
- Invalid submission (POST): errors are collected, and the same page shows the form again — with the user's input preserved and error messages next to the fields.
- Valid submission (POST): the data is processed, and the browser is redirected.
Because all logic for one form lives in one place, it is easy to follow. In larger applications, frameworks split this into a controller (processing) and a view (template), but the flow is the same.
The Processing Steps
| Step | What to Do | Lesson |
|---|---|---|
| 1. Check the method | Process only POST submissions | \$_SERVER |
| 2. Verify the CSRF token | Reject forged submissions | (below) |
| 3. Read input | Use defaults and type checks | \$_POST |
| 4. Validate | Required fields, formats, ranges, allowed values | PHP Form Validation |
| 5. Process | Save to the database, send email, etc. | — |
| 6. Respond | Redirect on success; re-display with errors on failure | Sticky Forms |
| 7. Escape output | htmlspecialchars() for every displayed value | Escaping Output |
Keep Validation Testable
Putting validation in a function that receives an array (instead of reading $_POST directly) makes it easy to test and reuse:
<?php
function inputString(array $input, string $key): string
{
$value = $input[$key] ?? '';
return is_string($value) ? trim($value) : '';
}
function validateFeedback(array $input): array
{
$data = [
'name' => inputString($input, 'name'),
'rating' => filter_var($input['rating'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 5],
]),
'comment' => inputString($input, 'comment'),
];
$errors = [];
if ($data['name'] === '') {
$errors['name'] = 'Name is required.';
}
if ($data['rating'] === false) {
$errors['rating'] = 'Choose a rating from 1 to 5.';
}
if (mb_strlen($data['comment']) < 10) {
$errors['comment'] = 'Comment must be at least 10 characters.';
}
return [$data, $errors];
}
[$data, $errors] = validateFeedback(['name' => ' Asha ', 'rating' => '5', 'comment' => 'Great PHP lessons!']);
var_dump($errors === []);
echo "{$data['name']} rated {$data['rating']}\n";
[, $errors] = validateFeedback(['name' => '', 'rating' => '9', 'comment' => 'Bad']);
print_r($errors);
// In the real page: [$data, $errors] = validateFeedback($_POST);
Output
bool(true)
Asha rated 5
Array
(
[name] => Name is required.
[rating] => Choose a rating from 1 to 5.
[comment] => Comment must be at least 10 characters.
)
- The function returns both the cleaned data (trimmed strings, an integer rating) and the errors, keyed by field name.
- Valid data comes back typed and normalized:
' Asha 'becomes'Asha', and'5'becomes5. - All errors are reported together, so the user can fix everything in one attempt.
Protecting Forms
Post/Redirect/Get (PRG)
After a successful POST, always redirect (with status 303) instead of rendering a page directly. Otherwise, refreshing the result page asks the browser to resubmit the form, which can create duplicate orders or messages. See \$_POST.
CSRF Tokens
A Cross-Site Request Forgery attack makes a logged-in user's browser submit a form to your site from a malicious page. A secret token stored in the session and included in every form prevents this:
<?php
session_start();
function csrfToken(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function isValidCsrfToken(mixed $token): bool
{
return is_string($token)
&& isset($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && !isValidCsrfToken($_POST['csrf_token'] ?? null)) {
http_response_code(419);
exit('Your session has expired. Please reload the page and try again.');
}
?>
<form method="post" action="">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrfToken()) ?>">
<!-- other fields -->
</form>
- The token is random and unique per session; a malicious site cannot read it from your page.
- Every POST must include the token; requests without a matching token are rejected before any processing.
hash_equals()compares the tokens in constant time.- Status 419 is a convention used by some frameworks (Laravel) for expired tokens; 403 is also common.
Other Safeguards
- Serve forms over HTTPS.
- Limit the rate of submissions for login and contact forms (to slow down bots and password guessing).
- Use a CAPTCHA or honeypot field for public forms that attract spam.
- Never trust hidden fields or select values — validate them on the server.
Common Mistakes
Fields Without name
Effect: Values are not submitted.
Fix: Every input needs a name attribute.
Trusting Browser Validation
Effect: required, type="email", and maxlength are bypassed by direct requests.
Fix: Validate everything on the server; use HTML validation only for user convenience.
Printing Input Without Escaping
Effect: Cross-site scripting.
Fix: htmlspecialchars() on every value you output.
Showing a Result Page Directly After POST
Effect: Duplicate submissions on refresh.
Fix: Redirect after success (PRG).
Losing User Input on Errors
Effect: Frustrated users who must retype everything.
Fix: Refill the form with the submitted (escaped) values — see Sticky Forms.
Best Practices
- Use GET for retrieving data and POST for changing data.
- Process POST requests at the top of the script, before any output.
- Validate in a reusable function that returns cleaned data and errors.
- Protect every POST form with a CSRF token, and redirect after success.
- Escape all output, and keep the user's input when showing errors.
Official reference: https\://www.php.net/manual/en/tutorial.forms.php