PHP Forms for Beginners: Collect and Validate User Input
Forms are the primary way users interact with web applications. Whether you are typing a search query on Google, entering your login credentials on a portal, or purchasing a course on CodigoBasePro, you are using an HTML form. However, while HTML ...
Forms are the primary way users interact with web applications. Whether you are typing a search query on Google, entering your login credentials on a portal, or purchasing a course on CodigoBasePro, you are using an HTML form. However, while HTML handles the layout and visual structure of a form, it cannot process the data. That is where PHP comes in. By combining HTML forms with PHP processing, you can collect user inputs, validate them for correctness, and handle them securely on the server. In this guide, we will walk you through building, capturing, and validating forms in PHP.
For beginners, form handling can seem intimidating because it involves both frontend layouts and backend parsing. Additionally, accepting user inputs introduces serious security risks. A malicious user can submit harmful scripts or database queries through your input fields. Writing secure form handling code is one of the most critical skills for any backend web developer. Let's break down how to handle forms step-by-step.
The Mechanics of Form Submission: GET vs. POST
An HTML form uses the action and method attributes to define where and how data is sent. The action specifies the URL of the PHP script that processes the form, and the method specifies the HTTP request type. The two most common methods are GET and POST.
- GET Method: Form data is appended directly to the URL as query parameters (e.g.,
process.php?name=Alex&age=25). Since the data is visible in the address bar, GET should **never** be used for sensitive data like passwords. It is best suited for non-sensitive actions like search filters or pagination, where users might want to bookmark the page. - POST Method: Form data is sent inside the HTTP request body, making it invisible in the URL. POST is more secure, has no size limitations, and is required for operations that change server state, such as creating user accounts or submitting checkout forms.
Superglobals in PHP: $_GET and $_POST
When a form is submitted, PHP automatically captures the inputs and populates them into associative arrays called superglobals. These arrays are accessible from anywhere in your script:
$_GET: An associative array containing variables passed via the HTTP GET method.$_POST: An associative array containing variables passed via the HTTP POST method.$_SERVER['REQUEST_METHOD']: Used to detect if the page was accessed via a POST submission or a standard page load (GET).
Sanitization and Validation: The Shield of Your App
The golden rule of backend development is: **Never trust user input**. You must always sanitize and validate all inputs before using them in calculations, saving them to a database, or rendering them back to the screen.
- Sanitization: The process of cleaning input data by removing illegal characters, HTML tags, or script elements. For example, sanitizing an email means removing spaces and invalid symbols.
- Validation: The process of checking if the input data conforms to specific rules. For example, verifying if an email address is formatted correctly, if a age field contains only numbers, or if a password meets minimum length requirements.
PHP provides a powerful built-in tool for this: the filter_var() function. You can use filters like FILTER_SANITIZE_EMAIL, FILTER_VALIDATE_EMAIL, and FILTER_VALIDATE_INT to handle cleanups and checks quickly.
Deep Dive: XSS and CSRF Protection in PHP Forms
Accepting user inputs exposes your site to severe vulnerabilities, most notably Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF).
**Cross-Site Scripting (XSS)** occurs when a developer renders raw user inputs back to the browser without escaping. For example, if a user submits a username like <script>alert('Hacked!');</script> and your PHP prints it using echo $username;, the browser will execute the script. This can allow attackers to steal session cookies and take over accounts. To prevent XSS, you must always escape output using the htmlspecialchars() function. This converts special characters like < and > into HTML entities (< and >), rendering them harmlessly on the screen.
**Cross-Site Request Forgery (CSRF)** occurs when a malicious website tricks a logged-in user's browser into submitting a form request to your application. Because the browser automatically includes the user's login cookies, your server processes the request as authentic. To block CSRF, developers use **CSRF Tokens**. When rendering a form, PHP generates a unique, cryptographically secure random token, saves it in the user's session, and includes it as a hidden input field in the form. When the form is submitted, the PHP processing script compares the submitted token with the session token. If they do not match, the request is rejected immediately, keeping your application safe.
Practical Code Example
Let's look at a complete, secure PHP script that displays a registration form, validates inputs, sanitizes them, prevents XSS, and simulates registration logic.
<?php
session_start();
// Initialize error and success messages
$errors = [];
$successMessage = "";
// Generate CSRF Token if not present
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Check if form was submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validate CSRF Token
$submittedToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $submittedToken)) {
die("CSRF validation failed. Request blocked.");
}
// Capture and clean inputs
$nameInput = trim($_POST['username'] ?? '');
$emailInput = trim($_POST['email'] ?? '');
$ageInput = trim($_POST['age'] ?? '');
// 1. Validate Username
if (empty($nameInput)) {
$errors['username'] = "Username is required.";
} elseif (strlen($nameInput) < 3) {
$errors['username'] = "Username must be at least 3 characters.";
}
// 2. Validate Email
if (empty($emailInput)) {
$errors['email'] = "Email is required.";
} else {
$cleanEmail = filter_var($emailInput, FILTER_SANITIZE_EMAIL);
if (!filter_var($cleanEmail, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "Please enter a valid email address.";
}
}
// 3. Validate Age
if (empty($ageInput)) {
$errors['age'] = "Age is required.";
} else {
$ageInt = filter_var($ageInput, FILTER_VALIDATE_INT);
if ($ageInt === false || $ageInt < 13 || $ageInt > 120) {
$errors['age'] = "Age must be a valid number between 13 and 120.";
}
}
// Process if no errors
if (empty($errors)) {
// Sanitize name for display
$safeUsername = htmlspecialchars($nameInput, ENT_QUOTES, 'UTF-8');
$successMessage = "Welcome, $safeUsername! Your account was registered successfully.";
// Regenerate CSRF Token after successful action for safety
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Registration - CodigoBasePro</title>
<style>
body { font-family: Arial, sans-serif; background-color: #0b132b; color: #f8f9fa; padding: 30px; }
.form-card { background-color: #1c2541; border: 1px solid #5b5075; border-radius: 8px; padding: 25px; max-width: 450px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="email"] { width: 100%; padding: 8px; border: 1px solid #5b5075; border-radius: 4px; background: #0b132b; color: #fff; box-sizing: border-box; }
.error { color: #e65c5c; font-size: 0.85em; margin-top: 5px; }
.success { background: #1c3d5a; border: 1px solid #06b6d4; padding: 15px; border-radius: 4px; margin-bottom: 15px; text-align: center; }
button { background-color: #06b6d4; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; width: 100%; font-weight: bold; }
button:hover { background-color: #0891b2; }
</style>
</head>
<body>
<div class="form-card">
<h2 style="text-align: center; color: #06b6d4;">Register Account</h2>
<?php if (!empty($successMessage)): ?>
<div class="success"><?= $successMessage ?></div>
<?php endif; ?>
<form action="" method="POST">
<!-- CSRF Hidden Input -->
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="<?= htmlspecialchars($nameInput ?? '') ?>">
<?php if (isset($errors['username'])): ?>
<div class="error"><?= $errors['username'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" value="<?= htmlspecialchars($emailInput ?? '') ?>">
<?php if (isset($errors['email'])): ?>
<div class="error"><?= $errors['email'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="text" id="age" name="age" value="<?= htmlspecialchars($ageInput ?? '') ?>">
<?php if (isset($errors['age'])): ?>
<div class="error"><?= $errors['age'] ?></div>
<?php endif; ?>
</div>
<button type="submit">Create Account</button>
</form>
</div>
</body>
</html>
Line-by-Line Explanation
Let's trace how PHP handles form submissions and errors in this script:
- Line 2: We call
session_start()to boot sessions, enabling us to store the CSRF token securely between requests. - Lines 9-11: If no CSRF token exists in the user's session, we generate a random 32-byte hexadecimal string using the cryptographically secure
random_bytes()function. - Line 14: We check if the request method is POST. This prevents validation errors from running on the initial GET request when loading the page.
- Lines 17-20: We fetch the submitted token and compare it with the session token using
hash_equals(). This function runs in constant time, preventing timing attacks. - Lines 23-25: We fetch values from
$_POSTand clean them withtrim()to remove accidental leading or trailing spaces. - Lines 33-38: We validate the email. We first sanitize it using
FILTER_SANITIZE_EMAIL, and then check its structure usingFILTER_VALIDATE_EMAILinside thefilter_var()function. - Lines 41-47: We validate the age. Since inputs are strings, we check if it is a valid integer and check bounds (must be between 13 and 120).
- Line 52: If the
$errorsarray remains empty, we prepare the output. We wrap the name inhtmlspecialchars()to convert special symbols into HTML entities, protecting the output from XSS injection.
Common Mistakes Beginners Make
Form handling is a common source of bugs. Avoid these three common issues:
1. Relying Only on Frontend HTML Validation
HTML attributes like required, type="email", and maxlength="20" are great for user experience, but they are not secure. Anyone can right-click, open browser inspector, and delete these constraints, or bypass your forms entirely by sending direct POST requests. Always perform complete validation on the server side using PHP.
2. Rendering Unsanitized Inputs Directly
Printing form values back on the screen (e.g., in a search results page) using echo $_GET['search']; opens your website to XSS. If a user inputs a script, it will execute in other users' browsers. Always wrap outputs in htmlspecialchars().
3. Using the Wrong Request Method
Using GET for forms that modify databases or perform logins is a serious design mistake. GET values remain stored in browser histories, proxy logs, and are visible in screenshots. Always use POST for database writes, authentication, and secure checkout forms.
Troubleshooting Tips
If your form submissions are failing silently, check these details:
- Confirm input name attributes: In HTML, if you write
<input id="user_email">without aname="email"attribute, PHP will not capture the value.$_POST['email']will be completely empty. - Check Form Encoding: If your form has a file upload input but you forget to add
enctype="multipart/form-data"to your form tag, file uploads will fail completely. - Verify session_start(): If CSRF validation fails on every submission, verify that
session_start();is called at the very top of the script, before any HTML tags or blank spaces.
Practice Exercise
Create a secure contact form. Write a PHP script that contains fields for a user's name, telephone number, and message. Validate the following rules on submit:
- The telephone field must contain only numbers and be exactly 10 digits long.
- The message field must not be empty and must be at least 20 characters long.
- If the input fails, display specific errors. If it succeeds, show a success box with the sanitized username.
Conclusion
Capturing and validating user inputs is a fundamental requirement of backend web programming. By setting up proper POST methods, sanitizing inputs with filters, checking formatting on the server, and escaping all output streams using htmlspecialchars() to prevent XSS, you can write forms that are highly interactive, robust, and secure. In our next tutorial, we will learn how to persist login status across pages using PHP sessions.
Frequently Asked Questions
What is the difference between sanitization and validation?
Sanitization removes illegal or dangerous characters from data, altering the input. Validation checks if the data matches specific rules (e.g., character length or structure) and rejects the input if it does not match, without changing it.
Why should I use htmlspecialchars instead of strip_tags?
strip_tags() attempts to strip out HTML tags entirely, but it is not completely secure and can break text containing mathematical symbols (like x < y). htmlspecialchars() safely encodes all characters into harmless HTML entities without losing content.
How long does a CSRF token remain valid?
A CSRF token remains valid as long as the user's session remains active, though it is best practice to regenerate the token after successful state-changing operations (like logging in or submitting a payment form) to limit reuse windows.