LEARN TO CODE | PHP | 02 PAGES AND FORMS

02.3 Validating input and escaping output

Decide whether submitted data is acceptable, report errors clearly and display values without treating them as HTML markup.

01 | TWO DIFFERENT QUESTIONS

Continue using the PHP development server from 01.1. Create the files below inside one practice folder, preserving their filenames and subfolders. In VS Code open that folder, then start php -S 127.0.0.1:8000 in its terminal and open the named page through HTTP.

These are complete local examples for PHP 8. Text inputs and output are treated deliberately, but the examples are not a login system, a production deployment or a saved registration service. Where a page previews a submission only, nothing is sent or stored.

Validation asks whether a value meets the application’s rules. Output escaping asks how to represent a value safely in a particular output context.

A name containing an apostrophe may be perfectly acceptable. It still needs appropriate encoding when placed into HTML. Conversely, an escaped year of 500 is still an invalid year for a form allowing 7 to 13.

02 | DEFINE THE RULES FIRST

The request is a preview only
Field This exercise’s rule What it does not establish
Name Trimmed, 1–100 bytes A person’s real identity
Email Passes FILTER_VALIDATE_EMAIL Ownership or deliverability
Year Integer value, 7–13 inclusive That the person actually attends that year
Club One known option key That a registration was saved

The name limit uses strlen, which counts bytes, not user-perceived characters. We state that explicitly rather than promise a 100-character multilingual policy. A real product can choose an appropriate Unicode-aware length rule.

03 | CHECK THE INPUT SHAPE FIRST

Start with known expected keys. For each, reject a non-string submitted value rather than passing an array into trim or a string function.

Defaulting a missing value to '' lets the same field rules handle omissions. Trimming outer whitespace is a deliberate normalisation step; it does not prove the remaining content is trustworthy.

A fixed dropdown still needs an allow-list check. Someone can submit an option the HTML never offered.

04 | COMPLETE VALIDATED FORM

Use this shared helper wherever the guide requests helpers.php. It escapes strings for HTML text and quoted ordinary HTML attribute values; it is not a universal encoder for every context.

helpers.php

<?php
function escapeHtml(string $text): string
{
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

signup.php

<?php
require_once __DIR__ . '/helpers.php';
$values = ['name' => '', 'email' => '', 'year' => '', 'club' => ''];
$clubs = ['robotics' => 'Robotics', 'art' => 'Art & Design'];
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    foreach ($values as $key => $unused) {
        $raw = $_POST[$key] ?? '';
        if (!is_string($raw)) {
            $errors[] = 'Each field must contain a single text value.';
        } else {
            $values[$key] = trim($raw);
        }
    }
    if ($values['name'] === '' || strlen($values['name']) > 100) {
        $errors[] = 'Enter a name of 1 to 100 bytes.';
    }
    if (filter_var($values['email'], FILTER_VALIDATE_EMAIL) === false) {
        $errors[] = 'Enter a valid email address format.';
    }
    $year = filter_var($values['year'], FILTER_VALIDATE_INT,
        ['options' => ['min_range' => 7, 'max_range' => 13]]);
    if ($year === false) {
        $errors[] = 'Enter a whole-number year level from 7 to 13.';
    }
    if (!array_key_exists($values['club'], $clubs)) {
        $errors[] = 'Choose a listed club.';
    }
    $success = !$errors;
}
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Validate a club request</title></head>
<body>
<h1>Club request preview</h1>
<p>This exercise previews a request. It does not save or send it.</p>
<?php if ($errors): ?>
    <ul role="alert">
        <?php foreach ($errors as $error): ?><li><?= escapeHtml($error) ?></li><?php endforeach; ?>
    </ul>
<?php endif; ?>
<form action="signup.php" method="post">
    <p><label for="name">Name : </label>
    <input id="name" name="name" required value="<?= escapeHtml($values['name']) ?>"></p>
    <p><label for="email">Email : </label>
    <input id="email" name="email" type="email" required value="<?= escapeHtml($values['email']) ?>"></p>
    <p><label for="year">Year level : </label>
    <input id="year" name="year" type="number" min="7" max="13" required value="<?= escapeHtml($values['year']) ?>"></p>
    <p><label for="club">Club : </label>
    <select id="club" name="club" required>
        <option value="">Choose a club</option>
        <?php foreach ($clubs as $key => $label): ?>
            <option value="<?= escapeHtml($key) ?>" <?= $values['club'] === $key ? 'selected' : '' ?>><?= escapeHtml($label) ?></option>
        <?php endforeach; ?>
    </select></p>
    <button type="submit">Preview request</button>
</form>
<?php if ($success): ?>
    <h2>Validated preview</h2>
    <p><?= escapeHtml($values['name']) ?> requested <?= escapeHtml($clubs[$values['club']]) ?>.</p>
<?php endif; ?>
</body>
</html>

05 | VALIDATE WITHOUT DESTROYING THE INPUT

The processor stores the current field values, builds an errors list and declares success only when no errors remain. The HTML then displays errors and reuses the entered values.

filter_var with FILTER_VALIDATE_INT returns the integer or false for this usage. Compare with === false so a legitimate zero is not accidentally confused with failure in a different numeric range.

Our permitted year range excludes zero anyway, but the strict comparison expresses the correct general distinction.

06 | ALLOW-LISTS LIMIT THE MEANING

The club field sends a key such as robotics. PHP checks that it exists in the server’s known $clubs array, then uses the corresponding label.

This prevents an unexpected key from being treated as a valid club. It does not grant permission or check available places; those are separate requirements.

Use a fixed mapping for known choices instead of trusting submitted display labels or deciding validity solely from what the browser showed.

07 | ESCAPE AT THE OUTPUT POINT

Keep ordinary values as data, then escape when placing them into HTML. The helper transforms special characters into HTML representations so the browser treats them as text.

The example escapes both paragraph content and values in quoted input attributes. It also escapes option values and labels. The selected attribute is a fixed string chosen by a server-side comparison, not raw submitted markup.

Do not store a pre-escaped HTML version as the only copy of every value. A different destination may need different handling, and repeated escaping can produce confusing display.

08 | CONTEXT STILL MATTERS

htmlspecialchars is appropriate for the ordinary HTML text and quoted attribute contexts shown here. It does not by itself validate a URL scheme or make text safe inside a JavaScript program, CSS expression or SQL statement.

For database values, use parameterised queries; for allowed URL destinations, apply appropriate validation and URL construction. Learn each boundary when you introduce that feature.

Escaping is also different from stripping tags. Data can contain meaningful angle brackets that should be displayed as text rather than silently deleted.

09 | TEST THE RULE, NOT JUST THE BUTTON

Boundary and malformed-input tests
Test Expected
Valid fields and year 7 or 13 Validated preview
Year 6 or 14 Range error
Year 10.5 Integer error
Unknown club key Choice error
Whitespace-only name Name error
Array submitted as name Shape error, no crash
Name containing angle brackets Displays as text if other rules pass
Email-like text that fails validation Email-format error

Use fictitious example data. The page does not mail or save a request. Testing the preview is not evidence of a completed registration workflow.

10 | INDEPENDENT PRACTICE

TRY IT YOURSELF

Add an optional short comment field. Choose a byte-length limit, preserve its value after errors and display it in the preview as ordinary text.

Reveal guidance

Add comment to the values map, include a textarea with escaped content, validate the chosen length using the same clearly stated byte rule, and escape it again at display. Do not reject all punctuation merely to avoid understanding output encoding.

Next, we will remember a small choice across requests using server-side session state.

KEEP LEARNING

Previous page 02.4 Sessions and cookies

Official references: Includes and control structures · POST values · HTML escaping · Sessions · Cookies