PHP Forms — Form Handling, Validation, Security, and File Uploads
In this tutorial, you will learn about PHP Forms. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP forms accept user input through HTML form elements, process it server-side with validation and sanitization, and return feedback while preventing common attacks like XSS, CSRF, and SQL injection.
Why It Matters
Forms are how users interact with your application. A contact form, registration page, search box, checkout process -- every user-facing feature involves form handling. Poorly handled forms lead to bad user experience, data loss, and security vulnerabilities. Understanding form validation, error display, and data persistence across submissions is essential for building professional web applications.
Real-World Use
WordPress contact forms handle millions of submissions daily with spam filtering and file attachments. E-commerce checkout forms validate credit cards and shipping addresses. Login forms authenticate users and manage sessions. Laravel's form validation handles complex rules like "required_if" and "unique:users,email". DodaTech's support portal uses validated forms for ticket submission with file attachments.
What You Will Learn
- Creating HTML forms that submit to PHP
- GET vs POST methods and when to use each
- Form validation: required fields, email format, numeric range
- Displaying validation errors and preserving form state
- CSRF protection for form submissions
- Secure file upload handling
- Preventing XSS and SQL injection in form data
Learning Path
flowchart LR A[Superglobals] --> B[Forms
You are here] B --> C[Sessions] C --> D[Cookies] D --> E[Headers] style B fill:#f90,color:#fff
Basic Form Structure
An HTML form that submits to PHP:
<form method="post" action="process.php">
<label for="name">Name:</label>
<input type="text" name="name" id="name" required>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required>
<button type="submit">Submit</button>
</form>
The method="post" attribute tells the browser to send data in the request body. The action attribute specifies which PHP file receives the data. If empty or omitted, the form submits to the current page, which is the common pattern for self-processing forms.
Self-Processing Form Pattern
A single file that both displays the form and processes it:
<?php
$name = $email = '';
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
if (empty($name)) {
$errors['name'] = 'Name is required';
}
if (empty($email)) {
$errors['email'] = 'Email is required';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Invalid email format';
}
if (empty($errors)) {
$success = true;
// Process the form (save to database, send email, etc.)
}
}
?>
<!DOCTYPE html>
<html>
<body>
<?php if ($success): ?>
<p>Thank you, <?= htmlspecialchars($name) ?>! Form submitted.</p>
<?php else: ?>
<form method="post">
<div>
<label>Name:</label>
<input type="text" name="name"
value="<?= htmlspecialchars($name) ?>">
<?php if (isset($errors['name'])): ?>
<span class="error"><?= $errors['name'] ?></span>
<?php endif; ?>
</div>
<div>
<label>Email:</label>
<input type="email" name="email"
value="<?= htmlspecialchars($email) ?>">
<?php if (isset($errors['email'])): ?>
<span class="error"><?= $errors['email'] ?></span>
<?php endif; ?>
</div>
<button type="submit">Submit</button>
</form>
<?php endif; ?>
</body>
</html>
The form preserves user input using value="<?= htmlspecialchars($name) ?>". This way, if validation fails, the user does not have to retype everything.
GET vs POST
| Aspect | GET | POST |
|---|---|---|
| Data location | URL query string | Request body |
| Visibility | Visible in URL | Not visible |
| Bookmarkable | Yes | No |
| Size limit | ~2000 characters | No practical limit |
| Security | Low (data in URL) | Medium (body not cached) |
| Use case | Searches, filters | Login, forms, mutations |
Validation Techniques
Required Fields
if (empty($_POST['username'])) {
$errors['username'] = 'Username is required';
}
Email Validation
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
$errors['email'] = 'Invalid email address';
}
Numeric Range
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 120]
]);
if ($age === false) {
$errors['age'] = 'Age must be between 1 and 120';
}
String Length
$message = trim($_POST['message'] ?? '');
if (strlen($message) < 10) {
$errors['message'] = 'Message must be at least 10 characters';
}
if (strlen($message) > 1000) {
$errors['message'] = 'Message must not exceed 1000 characters';
}
URL Validation
$website = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);
if ($website !== false && $website !== null) {
// Valid URL provided
}
CSRF Protection
Cross-Site Request Forgery (CSRF) tricks users into submitting forms on your site from another site. Prevent it with tokens:
<?php
session_start();
// Generate token on form display
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
// Validate on submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$submittedToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($token, $submittedToken)) {
die('Invalid CSRF token');
}
// Process form
}
?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= $token ?>">
<!-- other fields -->
<button type="submit">Submit</button>
</form>
File Upload Handling
Files require special handling with the enctype attribute:
<form method="post" enctype="multipart/form-data">
<input type="file" name="document" accept=".pdf,.jpg,.png">
<button type="submit">Upload</button>
</form>
PHP processing:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$file = $_FILES['document'];
$errors = [];
// Check for upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors[] = 'Upload failed with code: ' . $file['error'];
}
// Validate file size (5MB max)
$maxSize = 5 * 1024 * 1024;
if ($file['size'] > $maxSize) {
$errors[] = 'File too large. Maximum is 5MB.';
}
// Validate file type by examining content
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mimeType, $allowedTypes)) {
$errors[] = 'Only JPG, PNG, and PDF files are allowed.';
}
if (empty($errors)) {
$safeName = bin2hex(random_bytes(16)) . '.' .
pathinfo($file['name'], PATHINFO_EXTENSION);
$dest = 'uploads/' . $safeName;
move_uploaded_file($file['tmp_name'], $dest);
echo "File uploaded successfully: $safeName";
} else {
foreach ($errors as $error) {
echo "<p>$error</p>";
}
}
}
Security Rules for File Uploads
- Never trust the
$_FILES['type']-- it is sent by the client - Use
finfoto detect the real file type - Generate a random filename, never use the original name
- Store files outside the web root when possible
- Limit file size both in PHP and in server configuration
Common Mistakes
1. Not Validating Server-Side
Client-side validation (HTML5 required, JavaScript) is easily bypassed. Always validate on the server.
2. Outputting User Input Without Escaping
echo "Welcome, " . $_POST['name']; // XSS vulnerability
Always use htmlspecialchars() or a template engine that auto-escapes.
3. Not Checking Request Method
if (isset($_POST['submit'])) { // Can be spoofed
Check $_SERVER['REQUEST_METHOD'] === 'POST' instead.
4. Using Extract on Superglobals
extract($_POST); // Creates variables $name, $email - dangerous!
Variables may overwrite existing ones. Access superglobals directly.
5. Ignoring File Upload Errors
if ($_FILES['file']['error'] === UPLOAD_ERR_INI_SIZE) {
// File exceeds upload_max_filesize in php.ini
}
Always check $file['error'] !== UPLOAD_ERR_OK before processing.
Practice Questions
What is the difference between GET and POST methods? GET sends data in the URL, is bookmarkable, has size limits. POST sends data in the body, has no size limit, and is not cached.
Why should you escape output from form submissions? To prevent XSS Attacks. The browser executes injected HTML/JavaScript if you output raw user input.
What does enctype="multipart/form-data" do? It tells the browser to encode the form for file uploads. Without it, files are not sent.
How do you prevent CSRF Attacks on forms? Use a unique token stored in the session, include it as a hidden field, and validate on submission.
Challenge: Build a complete registration form with name, email, password, password confirmation, age, and profile picture upload. Implement all validations and CSRF protection.
Mini Project: Contact Form with File Attachment
Build a secure contact form:
<?php
session_start();
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) {
die('Invalid token');
}
$name = trim($_POST['name'] ?? '');
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$subject = trim($_POST['subject'] ?? '');
$message = trim($_POST['message'] ?? '');
if (empty($name)) $errors[] = 'Name is required';
if ($email === false) $errors[] = 'Valid email required';
if (strlen($subject) < 3) $errors[] = 'Subject too short';
if (strlen($message) < 10) $errors[] = 'Message too short';
$attachment = '';
if (isset($_FILES['attachment']) && $_FILES['attachment']['error'] === UPLOAD_ERR_OK) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['attachment']['tmp_name']);
finfo_close($finfo);
if (in_array($mime, ['image/jpeg', 'image/png', 'application/pdf'])) {
$attachment = 'uploads/' . bin2hex(random_bytes(8));
move_uploaded_file($_FILES['attachment']['tmp_name'], $attachment);
} else {
$errors[] = 'Invalid file type';
}
}
if (empty($errors)) {
file_put_contents('contacts.log', implode('|', [
date('Y-m-d H:i:s'), $name, $email, $subject, $message, $attachment
]) . "\n", FILE_APPEND);
$success = true;
}
}
$_SESSION['csrf_token'] = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32));
?>
FAQ
{{< faq "How do I preserve form input after submission?" "Set the value attribute to the submitted value: value="= htmlspecialchars($_POST['name'] ?? '') ?>". This shows what the user typed." >}}
{{< faq "How do I handle multiple file uploads?" "Use array notation in the name: name=\"files[]\". Multiple file inputs with the same name create an array. Multiple attribute on input type=file also works." >}} {{< faq "Why does my form submit as GET even with method=post?" "Check for missing or malformed form tags. A form with method=\"post\" but action containing query parameters may append them." >}}What is Next
Now that you can handle forms, proceed to Sessions to learn how to persist user state across page requests. Then read Cookies to understand client-side data storage.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro