Phase 2 · Web FundamentalsModule 9~46 min read

Forms, GET & POST

Build accessible forms and process query strings and request bodies without mixing responsibilities.

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.

search.html
<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>
AttributeResponsibility
actionThe request target; an empty value submits to the current URL
methodGET for retrieval or POST for submitted commands
nameThe key included in request data
valueThe submitted value and initial control value
for / idAssociates a label with its control

Key idea

Browser validation improves the experience, but the server must validate again. Clients can modify or bypass every HTML attribute.

GET forms create shareable URLs

GET encodes named fields into the query string. That makes search, filtering, sorting, and pagination bookmarkable and shareable.

search.php
<?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

URLs are followed by crawlers, prefetched by browsers, copied into logs, and revisited from history. Never use a GET link to delete, purchase, publish, or log out.

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.

create-course.php
<?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?

A syntactically valid request with unacceptable field values is often represented by 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.

sticky-form.php
<?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.

save.php
<?php

declare(strict_types=1);

// Validate and save the record first.
$newId = 42;

header("Location: /courses/{$newId}?created=1", true, 303);
exit;

Tip

Always 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.

Request workflow
GET  → prepare defaults → render form
POST → collect input → normalize → validate
     ├─ invalid → render form with values + errors
     └─ valid   → perform command → redirect → GET confirmation

Recap & 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?