What you'll learn
Forms are the browser's built-in way to create HTTP requests. You'll build accessible forms, read GET and POST data, preserve user effort, and prevent duplicate submissions with redirects.
- Connect labels, controls, names, actions, and methods
- Use GET for shareable reads and POST for state-changing commands
- Read request data without assuming keys exist
- Redisplay submitted values safely
- Apply Post/Redirect/Get after successful processing
A form is a request builder
The action chooses the target URL and method chooses GET or POST. Only successful controls with a name are submitted. A label improves accessibility and makes the control easier to click.
<form action="/search.php" method="get">
<label for="query">Search courses</label>
<input id="query" name="q" type="search" required>
<label for="level">Level</label>
<select id="level" name="level">
<option value="">Any level</option>
<option value="beginner">Beginner</option>
<option value="advanced">Advanced</option>
</select>
<button type="submit">Search</button>
</form>| Attribute | Responsibility |
|---|---|
action | The request target; an empty value submits to the current URL |
method | GET for retrieval or POST for submitted commands |
name | The key included in request data |
value | The submitted value and initial control value |
for / id | Associates a label with its control |
Key idea
GET forms create shareable URLs
GET encodes named fields into the query string. That makes search, filtering, sorting, and pagination bookmarkable and shareable.
<?php
declare(strict_types=1);
$query = trim((string) ($_GET['q'] ?? ''));
$level = (string) ($_GET['level'] ?? '');
$allowedLevels = ['', 'beginner', 'advanced'];
if (!in_array($level, $allowedLevels, true)) {
$level = '';
}
echo 'Searching for: ' . htmlspecialchars($query, ENT_QUOTES, 'UTF-8');
echo '<br>Level: ' . htmlspecialchars($level, ENT_QUOTES, 'UTF-8');GET should not perform destructive actions
POST carries submitted commands
POST sends form fields in the request body. It is appropriate for operations that create or change state, but it does not make input private or trusted—HTTPS and server validation are still required.
<?php
declare(strict_types=1);
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($method !== 'POST') {
http_response_code(405);
header('Allow: POST');
exit('Method Not Allowed');
}
$title = trim((string) ($_POST['title'] ?? ''));
$minutes = filter_input(INPUT_POST, 'minutes', FILTER_VALIDATE_INT);
if ($title === '' || $minutes === false || $minutes < 1) {
http_response_code(422);
exit('Please submit a title and positive duration.');
}
echo "Created {$title} ({$minutes} min)";Why 422?
422 Unprocessable Content. Consistency matters more than chasing one universal convention.Preserve user effort with sticky fields
When validation fails, render the form again with safe submitted values and nearby error messages. Escape values because an input's value attribute is an HTML context.
<?php
$title = trim((string) ($_POST['title'] ?? ''));
$error = $title === '' && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST'
? 'A title is required.'
: null;
?>
<form method="post">
<label for="title">Course title</label>
<input
id="title"
name="title"
value="<?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>"
aria-describedby="title-error"
>
<?php if ($error !== null): ?>
<p id="title-error" role="alert">
<?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?>
</p>
<?php endif; ?>
<button>Save</button>
</form>Post/Redirect/Get prevents duplicate submissions
After a successful POST, redirect to a GET page. Refreshing then repeats the safe GET instead of resubmitting the command.
<?php
declare(strict_types=1);
// Validate and save the record first.
$newId = 42;
header("Location: /courses/{$newId}?created=1", true, 303);
exit;Tip
exit after sending a redirect unless the following code is intentionally safe to execute. A 303 explicitly tells the client to follow with GET.Separate display from processing
Even in one file, think in stages: detect method, collect input, validate, perform the command, redirect on success, then render the view. As the application grows, these stages become request objects, validators, services, controllers, and templates.
GET → prepare defaults → render form
POST → collect input → normalize → validate
├─ invalid → render form with values + errors
└─ valid → perform command → redirect → GET confirmationRecap & quick check
Key takeaways
- A form creates an HTTP request from named successful controls.
- Use GET for safe, shareable retrieval and POST for state-changing commands.
- Treat every submitted value as untrusted and every field as potentially missing.
- Sticky forms preserve safe values and connect errors to their controls.
- Redirect after a successful POST so refresh does not repeat the command.
Quick check
1. Which form field attribute becomes the submitted key?
2. Which method best fits a shareable product search?
3. What should follow a successful state-changing POST?
4. Why escape a sticky input value?