PHP Fundamentals
Form Validation
Form Validation
Form validation is essential for ensuring data quality, security, and a good user experience. PHP provides powerful tools for validating user input on the server side.
Critical Rule: NEVER trust client-side validation alone. JavaScript validation can be bypassed. Always validate on the server with PHP.
Types of Validation
There are two main types of validation:
- Client-side validation: JavaScript/HTML5 (fast, better UX, but can be bypassed)
- Server-side validation: PHP (secure, required, but slower feedback)
Best Practice: Use both! Client-side for immediate feedback, server-side for security.
Basic Validation Techniques
1. Required Fields
<?php
// Check if field is empty
if (empty($_POST['username'])) {
$errors[] = "Username is required";
}
// Check if field exists and not empty
if (!isset($_POST['email']) || trim($_POST['email']) === '') {
$errors[] = "Email is required";
}
// Alternative with better handling
$username = isset($_POST['username']) ? trim($_POST['username']) : '';
if ($username === '') {
$errors[] = "Username is required";
}
?>
2. String Length Validation
<?php
$username = trim($_POST['username']);
// Minimum length
if (strlen($username) < 3) {
$errors[] = "Username must be at least 3 characters";
}
// Maximum length
if (strlen($username) > 20) {
$errors[] = "Username must be less than 20 characters";
}
// Range
if (strlen($username) < 3 || strlen($username) > 20) {
$errors[] = "Username must be between 3 and 20 characters";
}
// For multibyte strings (Arabic, Chinese, etc.)
if (mb_strlen($username) < 3) {
$errors[] = "Username must be at least 3 characters";
}
?>
3. Email Validation
<?php
$email = trim($_POST['email']);
// Using filter_var (recommended)
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Invalid email format";
}
// Additional check: email exists and valid
if (empty($email)) {
$errors[] = "Email is required";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Invalid email format";
}
// Custom validation with regex (more strict)
if (!preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
$errors[] = "Invalid email format";
}
?>
4. Number Validation
<?php
$age = $_POST['age'];
// Check if numeric
if (!is_numeric($age)) {
$errors[] = "Age must be a number";
}
// Convert and validate range
$age = intval($age);
if ($age < 18 || $age > 120) {
$errors[] = "Age must be between 18 and 120";
}
// Validate positive numbers
$price = floatval($_POST['price']);
if ($price <= 0) {
$errors[] = "Price must be greater than zero";
}
// Using filter_var for integers
$quantity = filter_var($_POST['quantity'], FILTER_VALIDATE_INT);
if ($quantity === false || $quantity < 1) {
$errors[] = "Quantity must be a positive integer";
}
?>
5. URL Validation
<?php
$website = trim($_POST['website']);
// Using filter_var
if (!empty($website) && !filter_var($website, FILTER_VALIDATE_URL)) {
$errors[] = "Invalid URL format";
}
// Validate specific protocol
if (!empty($website)) {
if (!preg_match('/^https?:\/\//i', $website)) {
$errors[] = "URL must start with http:// or https://";
}
}
?>
6. Pattern Matching (Regex)
<?php
// Username: letters, numbers, underscore only
$username = $_POST['username'];
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
$errors[] = "Username can only contain letters, numbers, and underscores";
}
// Phone number (US format)
$phone = $_POST['phone'];
if (!preg_match('/^\d{3}-\d{3}-\d{4}$/', $phone)) {
$errors[] = "Phone must be in format: 123-456-7890";
}
// Strong password: min 8 chars, uppercase, lowercase, number, special char
$password = $_POST['password'];
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $password)) {
$errors[] = "Password must be at least 8 characters with uppercase, lowercase, number, and special character";
}
// Date format (YYYY-MM-DD)
$date = $_POST['date'];
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
$errors[] = "Date must be in format: YYYY-MM-DD";
}
?>
Complete Validation Example
<?php
// registration_validation.php
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get and sanitize input
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$password = $_POST['password'];
$confirm_password = $_POST['confirm_password'];
$age = isset($_POST['age']) ? $_POST['age'] : '';
$website = trim($_POST['website']);
$terms = isset($_POST['terms']);
// Validate username
if (empty($username)) {
$errors['username'] = "Username is required";
} elseif (strlen($username) < 3) {
$errors['username'] = "Username must be at least 3 characters";
} elseif (strlen($username) > 20) {
$errors['username'] = "Username must be less than 20 characters";
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
$errors['username'] = "Username can only contain letters, numbers, and underscores";
}
// Validate email
if (empty($email)) {
$errors['email'] = "Email is required";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "Invalid email format";
}
// Validate password
if (empty($password)) {
$errors['password'] = "Password is required";
} elseif (strlen($password) < 8) {
$errors['password'] = "Password must be at least 8 characters";
} elseif (!preg_match('/[A-Z]/', $password)) {
$errors['password'] = "Password must contain at least one uppercase letter";
} elseif (!preg_match('/[a-z]/', $password)) {
$errors['password'] = "Password must contain at least one lowercase letter";
} elseif (!preg_match('/[0-9]/', $password)) {
$errors['password'] = "Password must contain at least one number";
}
// Validate password confirmation
if ($password !== $confirm_password) {
$errors['confirm_password'] = "Passwords do not match";
}
// Validate age
if (empty($age)) {
$errors['age'] = "Age is required";
} elseif (!is_numeric($age)) {
$errors['age'] = "Age must be a number";
} else {
$age = intval($age);
if ($age < 18) {
$errors['age'] = "You must be at least 18 years old";
} elseif ($age > 120) {
$errors['age'] = "Please enter a valid age";
}
}
// Validate website (optional field)
if (!empty($website) && !filter_var($website, FILTER_VALIDATE_URL)) {
$errors['website'] = "Invalid website URL";
}
// Validate terms acceptance
if (!$terms) {
$errors['terms'] = "You must accept the terms and conditions";
}
// If no errors, process registration
if (empty($errors)) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Save to database here
$success = true;
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Registration with Validation</title>
<style>
.error { color: red; font-size: 0.9em; }
.field-error { border: 2px solid red; }
.success { color: green; padding: 10px; background: #d4edda; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="url"] {
padding: 8px;
width: 100%;
max-width: 400px;
}
</style>
</head>
<body>
<h1>User Registration</h1>
<?php if ($success): ?>
<div class="success">
<h2>Registration Successful!</h2>
<p>Welcome, <?php echo htmlspecialchars($username); ?>!</p>
</div>
<?php else: ?>
<form method="post" action="" novalidate>
<div class="form-group">
<label for="username">Username *</label>
<input type="text" id="username" name="username"
class="<?php echo isset($errors['username']) ? 'field-error' : ''; ?>"
value="<?php echo isset($username) ? htmlspecialchars($username) : ''; ?>">
<?php if (isset($errors['username'])): ?>
<div class="error"><?php echo $errors['username']; ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email"
class="<?php echo isset($errors['email']) ? 'field-error' : ''; ?>"
value="<?php echo isset($email) ? htmlspecialchars($email) : ''; ?>">
<?php if (isset($errors['email'])): ?>
<div class="error"><?php echo $errors['email']; ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="password">Password *</label>
<input type="password" id="password" name="password"
class="<?php echo isset($errors['password']) ? 'field-error' : ''; ?>">
<?php if (isset($errors['password'])): ?>
<div class="error"><?php echo $errors['password']; ?></div>
<?php endif; ?>
<small>Min 8 characters, uppercase, lowercase, and number</small>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password *</label>
<input type="password" id="confirm_password" name="confirm_password"
class="<?php echo isset($errors['confirm_password']) ? 'field-error' : ''; ?>">
<?php if (isset($errors['confirm_password'])): ?>
<div class="error"><?php echo $errors['confirm_password']; ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="age">Age *</label>
<input type="number" id="age" name="age"
class="<?php echo isset($errors['age']) ? 'field-error' : ''; ?>"
value="<?php echo isset($age) ? htmlspecialchars($age) : ''; ?>">
<?php if (isset($errors['age'])): ?>
<div class="error"><?php echo $errors['age']; ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="website">Website (optional)</label>
<input type="url" id="website" name="website"
class="<?php echo isset($errors['website']) ? 'field-error' : ''; ?>"
value="<?php echo isset($website) ? htmlspecialchars($website) : ''; ?>"
placeholder="https://example.com">
<?php if (isset($errors['website'])): ?>
<div class="error"><?php echo $errors['website']; ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<input type="checkbox" id="terms" name="terms" value="1">
<label for="terms" style="display: inline;">I agree to the terms *</label>
<?php if (isset($errors['terms'])): ?>
<div class="error"><?php echo $errors['terms']; ?></div>
<?php endif; ?>
</div>
<button type="submit">Register</button>
</form>
<?php endif; ?>
</body>
</html>
Validation Helper Functions
<?php
// Create reusable validation functions
function validateRequired($value, $fieldName) {
if (empty(trim($value))) {
return "$fieldName is required";
}
return null;
}
function validateLength($value, $min, $max, $fieldName) {
$length = strlen($value);
if ($length < $min) {
return "$fieldName must be at least $min characters";
}
if ($length > $max) {
return "$fieldName must be less than $max characters";
}
return null;
}
function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return "Invalid email format";
}
return null;
}
function validatePassword($password) {
$errors = [];
if (strlen($password) < 8) {
$errors[] = "at least 8 characters";
}
if (!preg_match('/[A-Z]/', $password)) {
$errors[] = "one uppercase letter";
}
if (!preg_match('/[a-z]/', $password)) {
$errors[] = "one lowercase letter";
}
if (!preg_match('/[0-9]/', $password)) {
$errors[] = "one number";
}
if (!empty($errors)) {
return "Password must contain " . implode(', ', $errors);
}
return null;
}
// Usage
$errors = [];
if ($error = validateRequired($_POST['username'], 'Username')) {
$errors['username'] = $error;
}
if ($error = validateLength($_POST['username'], 3, 20, 'Username')) {
$errors['username'] = $error;
}
if ($error = validateEmail($_POST['email'])) {
$errors['email'] = $error;
}
if ($error = validatePassword($_POST['password'])) {
$errors['password'] = $error;
}
?>
Sanitization vs Validation
Important Distinction:
- Validation: Checking if data meets requirements (reject if invalid)
- Sanitization: Cleaning data to make it safe (remove/escape dangerous characters)
<?php
// Sanitization examples
// Remove HTML tags
$clean_text = strip_tags($_POST['comment']);
// Sanitize email
$clean_email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
// Sanitize string (remove special chars)
$clean_string = filter_var($_POST['name'], FILTER_SANITIZE_STRING);
// Escape HTML for output (prevent XSS)
$safe_output = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
// Remove whitespace
$trimmed = trim($_POST['input']);
// Sanitize URL
$clean_url = filter_var($_POST['website'], FILTER_SANITIZE_URL);
// Convert to integer
$clean_number = intval($_POST['age']);
?>
CSRF Protection
Cross-Site Request Forgery (CSRF) protection prevents attackers from submitting forms on behalf of users.
<?php
// Generate CSRF token
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Add to form
?>
<form method="post">
<input type="hidden" name="csrf_token"
value="<?php echo $_SESSION['csrf_token']; ?>">
<!-- Other fields -->
</form>
<?php
// Validate CSRF token on submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) ||
$_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('Invalid CSRF token');
}
// Process form...
}
?>
Practice Exercise
Task: Create a contact form with validation for:
- Name (required, 2-50 characters, letters and spaces only)
- Email (required, valid email format)
- Phone (optional, format: XXX-XXX-XXXX)
- Subject (required, select from: General, Support, Sales)
- Message (required, 10-500 characters)
Requirements:
- Display field-specific error messages
- Highlight invalid fields with red border
- Preserve form values on error
- Show success message after validation
- Add CSRF protection
Bonus: Create validation helper functions and add client-side HTML5 validation attributes.
Summary
In this lesson, you learned:
- Why server-side validation is critical
- Required field validation
- String length validation
- Email, URL, and number validation
- Pattern matching with regular expressions
- Password strength validation
- Creating reusable validation functions
- Difference between validation and sanitization
- Field-specific error messages
- CSRF token protection
Next, we'll learn about file uploads in PHP!