Lesson 3.3: Working with Forms and User Input

Forms are the primary way users communicate with your web applications. Every time someone logs in, creates an account, writes a review, or makes a purchase, they're using a form. Think of forms as the conversation between your users and your application—they tell you what they want, and you respond appropriately.

But here's the challenge: users are unpredictable. They'll leave fields empty, enter invalid data, try to break your system (intentionally or not), and submit information in ways you never expected. Learning to handle forms properly means building applications that are both user-friendly and bulletproof.

The difference between amateur and professional web applications often comes down to form handling. Amateur apps crash when users submit unexpected data. Professional apps guide users toward success, provide helpful feedback, and handle edge cases gracefully. This lesson will teach you how to build the latter.

Modern web applications rely heavily on user input—from simple contact forms to complex multi-step wizards. Every social media post, e-commerce purchase, and user profile update flows through form processing systems. Mastering these concepts is essential for building interactive, dynamic websites that users actually want to use.

Understanding HTTP and Form Submission

Before diving into PHP code, it's crucial to understand how forms actually work. When a user clicks "Submit," their browser packages up all the form data and sends it to your server using either GET or POST methods. Each method serves different purposes and has important security implications.

GET requests append form data to the URL, making them visible to everyone and suitable only for non-sensitive searches and filters. POST requests hide data in the request body, making them appropriate for passwords, personal information, and any action that changes data on your server.

<?php
// GET data appears in the URL: example.com?name=John&age=25
if (isset($_GET['name'])) {
    $searchTerm = $_GET['name'];
    echo "Searching for: $searchTerm\n";
}

// POST data is hidden from the URL
if (isset($_POST['username'])) {
    $username = $_POST['username'];
    echo "Username submitted: $username\n";
}
?>

The $_GET and $_POST superglobals automatically capture form data based on the submission method. Always use isset() to check if data exists before trying to use it—this prevents errors when the form hasn't been submitted yet.

Here's a simple search form example:

<?php
if (isset($_GET['search'])) {
    $query = $_GET['search'];
    echo "You searched for: " . htmlspecialchars($query);
}
?>

<form method="GET" action="">
    <input type="text" name="search" placeholder="Enter search term">
    <button type="submit">Search</button>
</form>

Notice how we use htmlspecialchars() when displaying user input—this prevents XSS attacks by ensuring any HTML code gets displayed as text instead of being executed.

Building Your First Contact Form

Let's create a practical contact form that demonstrates proper form handling, validation, and user feedback. This example will show you the complete cycle from HTML form to PHP processing.

<form method="POST" action="">
    <div>
        <label for="name">Name:</label>
        <input type="text" id="name" name="name" required>
    </div>

    <div>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
    </div>

    <div>
        <label for="message">Message:</label>
        <textarea id="message" name="message" rows="5" required></textarea>
    </div>

    <button type="submit">Send Message</button>
</form>

Now let's handle the form submission with PHP:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $message = trim($_POST['message'] ?? '');

    if (!empty($name) && !empty($email) && !empty($message)) {
        echo "Thank you, $name! Your message has been received.";
        // In a real app, you'd save this to a database or send an email
    } else {
        echo "Please fill in all fields.";
    }
}
?>

The $_SERVER['REQUEST_METHOD'] check ensures our processing code only runs when the form is actually submitted via POST. The ?? operator provides default empty strings if the form fields don't exist, preventing undefined index warnings.

Input Validation: Your First Line of Defense

Client-side validation (like the required attribute) improves user experience but never provides real security. Users can easily bypass HTML validation, so server-side validation in PHP is essential for maintaining data integrity and security.

<?php
function validateContactForm($data) {
    $errors = [];

    // Validate name
    $name = trim($data['name'] ?? '');
    if (empty($name)) {
        $errors[] = "Name is required";
    } elseif (strlen($name) < 2) {
        $errors[] = "Name must be at least 2 characters";
    }

    // Validate email
    $email = trim($data['email'] ?? '');
    if (empty($email)) {
        $errors[] = "Email is required";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "Please enter a valid email address";
    }

    return $errors;
}
?>

This validation function returns an array of error messages, making it easy to display specific feedback to users. Let's use it in a complete form handler:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $errors = validateContactForm($_POST);

    if (empty($errors)) {
        $name = htmlspecialchars($_POST['name']);
        $email = htmlspecialchars($_POST['email']);
        echo "Success! Thank you, $name.";
    } else {
        echo "Please fix these issues:\n";
        foreach ($errors as $error) {
            echo "• $error\n";
        }
    }
}
?>

This approach separates validation logic from display logic, making your code more organized and easier to maintain.

Handling Different Input Types

Modern forms use various input types beyond simple text fields. Each type requires specific validation and processing techniques to ensure data quality and security.

<?php
// Handling different input types
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    // Text input
    $username = trim($_POST['username'] ?? '');

    // Email input
    $email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);

    // Number input
    $age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT, [
        'options' => ['min_range' => 13, 'max_range' => 120]
    ]);

    // Checkbox (returns value only when checked)
    $newsletter = isset($_POST['newsletter']);

    // Radio buttons
    $gender = $_POST['gender'] ?? '';

    // Select dropdown
    $country = $_POST['country'] ?? '';

    echo "Username: $username\n";
    echo "Email: " . ($email ? $email : "Invalid") . "\n";
    echo "Age: " . ($age !== false ? $age : "Invalid") . "\n";
    echo "Newsletter: " . ($newsletter ? "Yes" : "No") . "\n";
}
?>

The filter_var() function provides powerful validation and sanitization options. It can validate emails, URLs, integers, and many other data types while also cleaning the input.

Here's how to handle a select dropdown properly:

<?php
$validCountries = ['US', 'CA', 'UK', 'AU', 'DE'];
$selectedCountry = $_POST['country'] ?? '';

if (in_array($selectedCountry, $validCountries)) {
    echo "Valid country selected: $selectedCountry";
} else {
    echo "Please select a valid country";
}
?>

Always validate against a list of expected values for dropdowns and radio buttons—never trust that the submitted value is one of your original options.

File Upload Handling

File uploads add complexity but are essential for many applications. Profile pictures, document attachments, and media uploads all require careful handling to ensure security and proper storage.

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $file = $_FILES['avatar'];

    // Check for upload errors
    if ($file['error'] === UPLOAD_ERR_OK) {
        $fileName = $file['name'];
        $fileSize = $file['size'];
        $fileType = $file['type'];
        $tmpName = $file['tmp_name'];

        echo "File uploaded successfully!\n";
        echo "Name: $fileName\n";
        echo "Size: " . number_format($fileSize / 1024, 2) . " KB\n";

    } else {
        echo "Upload failed. Please try again.";
    }
}
?>

For security, always validate file types and sizes:

<?php
function validateUpload($file) {
    $errors = [];

    // Check if file was uploaded
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $errors[] = "File upload failed";
        return $errors;
    }

    // Check file size (limit to 2MB)
    $maxSize = 2 * 1024 * 1024; // 2MB in bytes
    if ($file['size'] > $maxSize) {
        $errors[] = "File too large (max 2MB)";
    }

    // Check file type
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!in_array($file['type'], $allowedTypes)) {
        $errors[] = "Only JPEG, PNG, and GIF files allowed";
    }

    return $errors;
}
?>

Creating User-Friendly Error Messages

Good error handling doesn't just prevent crashes—it guides users toward successful form submission. Clear, specific error messages help users fix problems quickly instead of getting frustrated and leaving your site.

<?php
function displayFormErrors($errors) {
    if (empty($errors)) {
        return '';
    }

    $output = "<div class='error-messages'>\n";
    $output .= "<h3>Please fix the following issues:</h3>\n";
    $output .= "<ul>\n";

    foreach ($errors as $error) {
        $safeError = htmlspecialchars($error);
        $output .= "<li>$safeError</li>\n";
    }

    $output .= "</ul>\n</div>\n";
    return $output;
}

function displaySuccessMessage($message) {
    $safeMessage = htmlspecialchars($message);
    return "<div class='success-message'>$safeMessage</div>\n";
}
?>

Here's how to preserve user input when validation fails:

<?php
function getFieldValue($fieldName, $default = '') {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        return htmlspecialchars($_POST[$fieldName] ?? $default);
    }
    return $default;
}

// In your HTML form:
?>
<input type="text" name="name" value="<?php echo getFieldValue('name'); ?>">
<input type="email" name="email" value="<?php echo getFieldValue('email'); ?>">

This prevents users from having to re-enter all their information when they make a single mistake.

Building a Complete Registration Form

Let's combine everything we've learned into a comprehensive user registration system that demonstrates professional-level form handling.

<?php
class UserRegistration {
    private $errors = [];

    public function validateRegistration($data) {
        $this->errors = [];

        // Username validation
        $username = trim($data['username'] ?? '');
        if (empty($username)) {
            $this->errors['username'] = 'Username is required';
        } elseif (strlen($username) < 3) {
            $this->errors['username'] = 'Username must be at least 3 characters';
        } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
            $this->errors['username'] = 'Username can only contain letters, numbers, and underscores';
        }

        // Email validation
        $email = trim($data['email'] ?? '');
        if (empty($email)) {
            $this->errors['email'] = 'Email is required';
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->errors['email'] = 'Please enter a valid email address';
        }

        // Password validation
        $password = $data['password'] ?? '';
        if (empty($password)) {
            $this->errors['password'] = 'Password is required';
        } elseif (strlen($password) < 8) {
            $this->errors['password'] = 'Password must be at least 8 characters';
        }

        return empty($this->errors);
    }

    public function getErrors() {
        return $this->errors;
    }
}
?>

Using this validation class:

<?php
$registration = new UserRegistration();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($registration->validateRegistration($_POST)) {
        echo "Registration successful!";
        // Process the registration (save to database, send email, etc.)
    } else {
        $errors = $registration->getErrors();
        echo "Please fix these issues:\n";
        foreach ($errors as $field => $message) {
            echo "• $message\n";
        }
    }
}
?>

Security Best Practices

Form security goes beyond validation—it involves protecting against various attack vectors that malicious users might exploit. Understanding these principles helps you build applications that are safe for both your users and your server.

CSRF Protection

Cross-Site Request Forgery (CSRF) attacks trick users into submitting forms they didn't intend to submit. Protecting against this requires generating unique tokens for each form.

<?php
session_start();

function generateCSRFToken() {
    if (!isset($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function validateCSRFToken($token) {
    return isset($_SESSION['csrf_token']) &&
           hash_equals($_SESSION['csrf_token'], $token);
}

// In your form:
$csrfToken = generateCSRFToken();
?>
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">

Then validate the token on submission:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $submittedToken = $_POST['csrf_token'] ?? '';

    if (!validateCSRFToken($submittedToken)) {
        die('Invalid request. Please try again.');
    }

    // Process the form...
}
?>

Input Sanitization

Beyond validation, input sanitization removes or neutralizes potentially dangerous content before storing or displaying it.

<?php
function sanitizeInput($input) {
    // Remove any null bytes
    $input = str_replace(chr(0), '', $input);

    // Trim whitespace
    $input = trim($input);

    // Remove HTML and PHP tags
    $input = strip_tags($input);

    return $input;
}

function sanitizeForDatabase($input) {
    // Basic sanitization for database storage
    $input = sanitizeInput($input);

    // Remove potentially dangerous characters
    $input = preg_replace('/[<>"\']/', '', $input);

    return $input;
}
?>

Debugging Form Issues

When forms don't work as expected, systematic debugging helps identify and fix problems quickly. These techniques will save you hours of frustration.

<?php
// Debug helper function
function debugFormData() {
    echo "<h3>Debug Information</h3>\n";
    echo "<strong>Request Method:</strong> " . $_SERVER['REQUEST_METHOD'] . "\n";

    if (!empty($_POST)) {
        echo "<strong>POST Data:</strong>\n";
        echo "<pre>" . print_r($_POST, true) . "</pre>\n";
    }

    if (!empty($_GET)) {
        echo "<strong>GET Data:</strong>\n";
        echo "<pre>" . print_r($_GET, true) . "</pre>\n";
    }

    if (!empty($_FILES)) {
        echo "<strong>Uploaded Files:</strong>\n";
        echo "<pre>" . print_r($_FILES, true) . "</pre>\n";
    }
}

// Call this function during development to see what's being submitted
// debugFormData();
?>

Common form problems and their solutions:

<?php
// Problem: Form submits but no data received
// Solution: Check form method and action attributes

// Problem: Getting "undefined index" errors
// Solution: Always use isset() or ?? operator
$safeValue = $_POST['field_name'] ?? '';

// Problem: Data contains HTML entities
// Solution: Use htmlspecialchars() for output, not input processing
$displayValue = htmlspecialchars($userInput);

// Problem: File uploads not working
// Solution: Check form enctype and upload limits
ini_get('upload_max_filesize'); // Check PHP limits
ini_get('post_max_size');       // Check POST limits
?>

Key Takeaways

Form handling is where user experience meets application security. Always validate and sanitize user input, provide clear error messages, and protect against common attacks like XSS and CSRF. Remember that users make mistakes—build forms that guide them toward success rather than punishing errors.

The combination of proper validation, security measures, and user-friendly feedback creates professional applications that users trust and enjoy using. Practice with different input types, file uploads, and complex validation scenarios to build your confidence with form processing.

Your array and string manipulation skills from previous lessons are essential here—forms generate arrays of data that need cleaning, validation, and formatting before use. Master these fundamentals, and you'll be ready to build sophisticated user interfaces that handle real-world complexity gracefully.

In the next lesson, we'll explore file handling and uploads in more detail, building on the foundation you've established with form processing to create robust file management systems.