Validating Input

Check every value in PHP with filter_var and an allowlist, and collect all the errors instead of stopping at the first.

Syntaxfilter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed

Validation is deciding whether input is acceptable before you use it. Everything in $_GET, $_POST and the headers was written by whoever sent the request. The required and type="email" attributes in your HTML help a person using a browser, but a request can be sent with any content at all, so the check that counts is the one in PHP.

filter_var

filter_var() takes a value and a filter constant, and returns the filtered value or false when it does not pass. FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_URL, FILTER_VALIDATE_INT and FILTER_VALIDATE_FLOAT cover most form fields. FILTER_VALIDATE_INT takes min_range and max_range options, which turns "a whole number from 13 to 120" into one call that also converts the string to an int.

Collect every error

Returning at the first problem makes the visitor submit again once per mistake. Build an array keyed by field name, add a message as each check fails, and treat an empty array as success. That same array then supplies the message printed beside each input when the form is redrawn.

What trips people up

For a field with a fixed set of answers - a plan, a country, a sort order - compare against an allowlist using in_array($value, $allowed, true). A pattern that rejects known-bad values always misses the case nobody thought of.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Reject a sign-up form and show a message under each bad field at once.
  • Convert a page number from the query string into a bounded integer.
  • Restrict a sort order or plan name to a list your code defines.
  • Test a form's rules by calling the validator with a fixed array.

More examples

Filtering a whole form in one call

filter_var_array(array $array, array|int $options = FILTER_DEFAULT, bool $add_empty = true) applies a filter per key in one pass and returns a new array. A field that fails gives false; a field that was never submitted gives null, because $add_empty defaults to true. Only the keys in the definition come back, so unexpected fields in $_POST are dropped rather than carried along. Reach for it when a form has many fields validated the same simple way.

Example · php
<?php

declare(strict_types=1);

$definition = [
    'email' => FILTER_VALIDATE_EMAIL,
    'website' => FILTER_VALIDATE_URL,
    'age' => [
        'filter' => FILTER_VALIDATE_INT,
        'options' => ['min_range' => 13, 'max_range' => 120],
    ],
];

$clean = filter_var_array($_POST, $definition) ?: [];
$errors = [];

foreach (array_keys($definition) as $field) {
    $value = $clean[$field] ?? null;

    if ($value === null || $value === false) {
        $errors[$field] = ucfirst($field) . ' is not valid.';
    }
}

An allowlist for a fixed set of answers

A select element or a set of radio buttons restricts the choice in the browser, and not at all in the request. Checking membership of a list you control is the whole validation, and the third argument true makes the comparison strict so no type juggling can let something else through. Falling back to a known value means the code after the check always has a usable $plan.

Example · php
<?php

declare(strict_types=1);

$allowedPlans = ['free', 'pro', 'team'];

$plan = $_POST['plan'] ?? '';
$errors = [];

if (!in_array($plan, $allowedPlans, true)) {
    $errors['plan'] = 'Choose one of the listed plans.';
    $plan = 'free';
}

$monthlyPrice = match ($plan) {
    'free' => 0,
    'pro' => 12,
    'team' => 40,
};

Length, emptiness and the redirect after success

Length limits use mb_strlen() rather than strlen(), because strlen() counts bytes and an accented or non-Latin character takes more than one. The validator is a plain function taking an array, which means it can be called with a hand-built array in a test without a web server anywhere. Error messages are escaped on the way out like any other text that came from a person.

Example · php
<?php

declare(strict_types=1);

function validateComment(array $input): array
{
    $errors = [];

    $body = trim($input['body'] ?? '');
    $length = mb_strlen($body);

    if ($length === 0) {
        $errors['body'] = 'Write something first.';
    } elseif ($length < 10) {
        $errors['body'] = 'At least 10 characters.';
    } elseif ($length > 2000) {
        $errors['body'] = 'At most 2000 characters.';
    }

    if (filter_var($input['email'] ?? '', FILTER_VALIDATE_EMAIL) === false) {
        $errors['email'] = 'That email address is not valid.';
    }

    return $errors;
}

$errors = validateComment($_POST);

if ($errors === []) {
    // save the comment here
    header('Location: /thanks', true, 303);
    exit;
}

foreach ($errors as $message) {
    echo '<p>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p>';
}

Discussion

  • Be the first to comment on this lesson.