first php projects

Contact Form Handler

This increment moves from terminal input to a browser form. Build a contact page that renders on GET, validates on POST, rejects missing CSRF tokens, preserves safe input after validation errors, and redirects after success.

Use A Small File Shape

public/contact.php
src/ContactForm.php
templates/contact.php

Keep validation in src/ContactForm.php so the web entry point only translates the request into application work.

Validate Into A Predictable Shape

PHP example
<?php

declare(strict_types=1);

function validateContact(array $input): array
{
    $values = [
        'name' => trim((string) ($input['name'] ?? '')),
        'email' => trim((string) ($input['email'] ?? '')),
        'message' => trim((string) ($input['message'] ?? '')),
    ];

    $errors = [];

    if ($values['name'] === '') {
        $errors['name'] = 'Enter your name.';
    }

    if (filter_var($values['email'], FILTER_VALIDATE_EMAIL) === false) {
        $errors['email'] = 'Enter a valid email address.';
    }

    if ($values['message'] === '') {
        $errors['message'] = 'Enter a message.';
    }

    return [$values, $errors];
}

The handler should start a session, create a random token when one is absent, and compare the POST token with hash_equals() before accepting the message.

Escape At Render Time

The template must escape old values:

PHP example
<?php

function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

Use e($values['email']) in an attribute and e($values['message']) inside the textarea. Validation and escaping solve different problems: validation decides whether a value is acceptable; escaping prevents a value becoming HTML.

Verify The Browser Flow

Check:

  1. GET renders an empty form and CSRF token.
  2. Empty POST renders field errors and preserves safe values.
  3. Missing or wrong CSRF token is rejected.
  4. Valid POST stores or sends the message and redirects.
  5. Refreshing the success page does not submit again.

Practice

Practice: Build A Contact Form Handler

Implement the contact form flow from the lesson. Use a session-backed CSRF token and preserve escaped values after validation errors.

Requirements

  • Render name, email, and message fields.
  • Validate required fields and email format server-side.
  • Verify a session-backed CSRF token before processing.
  • Redirect after success to avoid duplicate submissions on refresh.
  • Client-side validation is helpful but not a security control.
  • Escape values when redisplaying them.
  • Do not log full message bodies without a retention reason.

Verify GET, rejected POST, missing-token POST, accepted POST, and refresh-after-success behavior.

Show solution
PHP example
<?php

session_start();

$_SESSION['csrf'] ??= bin2hex(random_bytes(32));

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $submittedToken = (string) ($_POST['_csrf'] ?? '');

    if (!hash_equals($_SESSION['csrf'], $submittedToken)) {
        http_response_code(403);
        exit('Invalid request token.');
    }

    [$values, $errors] = validateContact($_POST);

    if ($errors === []) {
        $_SESSION['flash'] = 'Message accepted.';
        header('Location: /contact.php?sent=1', true, 303);
        exit;
    }
}

Render the form with escaped old values and a hidden _csrf field. A successful POST redirects with 303 See Other; rejected input renders safely without processing the message.