PHP Form Validation & Basic Security
Why You Can Never Trust Form Input
The previous lesson covered how to receive form data, but there's a core principle every PHP developer needs to internalize: never trust user input. Anything submitted through a form, a URL parameter, or an API request could be empty, malformed, unexpected, or even deliberately malicious. Validation and security aren't optional extras — they're a fundamental part of handling forms correctly, not a separate advanced topic.
This lesson covers two closely related things: validation (checking that input meets the rules you expect) and basic security (protecting your application from common attacks through that input).
Required Field Validation
The most basic form of validation is checking that required fields aren't empty.
php
<?php
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$errors = [];
if (empty($name)) {
$errors[] = "Name is required.";
}
if (empty($email)) {
$errors[] = "Email is required.";
}
if (!empty($errors)) {
foreach ($errors as $error) {
echo $error . "\n";
}
} else {
echo "Form submitted successfully.";
}
Collecting errors into an array, rather than stopping at the first problem, is generally better practice — it lets you show the user everything that needs fixing at once, instead of making them resubmit repeatedly for one error at a time.
Validating Email Format
PHP includes a built-in filter specifically for validating email addresses, which is far more reliable than trying to write your own pattern-matching logic.
php
<?php
$email = $_POST['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Please enter a valid email address.";
} else {
echo "Email looks valid.";
}
filter_var() with FILTER_VALIDATE_EMAIL checks that the string follows a properly formatted email structure. It's worth noting this only validates the format — it doesn't confirm the email address actually exists or belongs to the person submitting the form.
Validating Numbers and Ranges
php
<?php
$age = $_POST['age'] ?? '';
if (!filter_var($age, FILTER_VALIDATE_INT)) {
echo "Age must be a valid number.";
} elseif ($age < 18 || $age > 100) {
echo "Age must be between 18 and 100.";
} else {
echo "Age is valid.";
}
FILTER_VALIDATE_INT confirms the submitted value is genuinely an integer before you attempt any range checks or calculations on it — an important step, since form input always arrives as a string, and a value like "25abc" could otherwise cause unexpected behavior.
Validating Against a Pattern (Regular Expressions)
For formats that don't have a built-in filter, such as a specific phone number pattern, regular expressions provide precise control.
php
<?php
$phone = $_POST['phone'] ?? '';
if (!preg_match('/^[0-9]{10}$/', $phone)) {
echo "Phone number must be exactly 10 digits.";
} else {
echo "Phone number is valid.";
}
This pattern (^[0-9]{10}$) requires exactly 10 numeric digits, with nothing before or after — a common validation requirement for Indian mobile numbers.
What is Sanitization, and How Is It Different from Validation?
Validation checks whether input meets your rules and rejects it if it doesn't. Sanitization is different — it cleans or modifies input to make it safe, rather than rejecting it outright. Both are typically used together.
Concept What It Does Example
------------------------------------------------------------
Validation Checks if input meets the rules, "Is this a
accepts or rejects it valid email?"
Sanitization Cleans/modifies input to remove Strips out HTML
potentially unsafe content tags from a
comment field
php
<?php $comment = $_POST['comment'] ?? ''; $cleanComment = htmlspecialchars($comment); echo $cleanComment;
Preventing Cross-Site Scripting (XSS)
One of the most important security habits in PHP is using htmlspecialchars() any time you display user-submitted content back on a page. Without it, a malicious user could submit HTML or JavaScript code as form input, which would then execute in other visitors' browsers when displayed — an attack known as Cross-Site Scripting (XSS).
php
<?php $comment = $_POST['comment'] ?? ''; // Unsafe: directly echoing user input // echo $comment; // Safe: converts special characters into harmless HTML entities echo htmlspecialchars($comment);
If a user submitted <script>alert('hacked')</script> as a comment, htmlspecialchars() converts the angle brackets into their safe HTML entity equivalents, so the browser displays the text literally instead of executing it as code. This single function is one of the most important habits to build early — apply it any time you output user-submitted data back onto a page.
Trimming and Normalizing Input
Users frequently submit data with accidental extra spaces, which can cause validation checks to behave unexpectedly (an email with a trailing space, for instance, might otherwise pass a check but fail to match later).
php
<?php $name = trim($_POST['name'] ?? ''); $email = trim($_POST['email'] ?? '');
Applying trim() consistently before validating or storing input is a small habit that prevents a surprising number of subtle bugs.
Whitelisting Expected Values
For fields with a fixed set of valid options, like a dropdown, it's good practice to verify the submitted value is actually one of the expected choices, rather than trusting it blindly — since form data can be manipulated outside of the browser.
php
<?php
$country = $_POST['country'] ?? '';
$allowedCountries = ['IN', 'US', 'UK'];
if (!in_array($country, $allowedCountries)) {
echo "Invalid country selection.";
} else {
echo "Country accepted.";
}
Even though a dropdown limits what a user can see as options, nothing stops someone from submitting a different value directly, bypassing the HTML entirely. Server-side validation like this is what actually enforces the rule.
A Note on Passwords
Password fields deserve special mention: passwords should never be stored as plain, readable text. PHP provides a built-in function specifically for this purpose:
php
<?php
$password = $_POST['password'] ?? '';
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Store $hashedPassword in the database, never the original password
// Later, when verifying a login attempt:
if (password_verify($password, $hashedPassword)) {
echo "Password matches.";
}
password_hash() converts a password into a scrambled, irreversible format, and password_verify() checks a plain password attempt against that stored hash without ever needing to store or compare the original text. This is a non-negotiable practice for any application handling user accounts.
Putting It All Together: A Validated Form
php
<?php
$errors = [];
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$age = $_POST['age'] ?? '';
if (empty($name)) {
$errors[] = "Name is required.";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "A valid email is required.";
}
if (!filter_var($age, FILTER_VALIDATE_INT) || $age < 18) {
$errors[] = "Age must be a number, 18 or older.";
}
if (empty($errors)) {
$safeName = htmlspecialchars($name);
echo "Welcome, $safeName!";
} else {
foreach ($errors as $error) {
echo $error . "\n";
}
}
This example combines required-field checks, format validation, and safe output into a single realistic flow, closely resembling how a real signup or contact form would be handled.
What's Next
- With forms, validation, and basic security covered, you now have the tools to safely collect and process real user input, one of the most common tasks in any PHP application. From here, the natural next step is learning object-oriented PHP, which introduces a more structured way to organize the logic you've been writing so far — including form handling — into reusable classes.