PHP Fundamentals

Form Validation

13 min Lesson 18 of 45

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:

  1. Name (required, 2-50 characters, letters and spaces only)
  2. Email (required, valid email format)
  3. Phone (optional, format: XXX-XXX-XXXX)
  4. Subject (required, select from: General, Support, Sales)
  5. 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!