A PHP form handler accepts a defined request shape, normalizes without changing intent, validates domain rules, reports accessible field errors, encodes only at output, and persists only trusted values.
Production forms also authorize every option and resource, verify CSRF protection, constrain files and request complexity, prevent duplicate commands, redirect after success, and test malformed requests with zero unintended side effects.
| Method | Good fit | Browser behavior |
|---|---|---|
| GET | Search, filtering, pagination | Values appear in the URL and can be bookmarked |
| POST | State-changing submissions | Values are sent in the request body |
<?php
$name = trim((string) ($_POST['name'] ?? ''));
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$errors = [];
if ($name === '') {
$errors['name'] = 'Enter your name.';
}
if ($email === false || $email === null) {
$errors['email'] = 'Enter a valid email address.';
}
if ($errors === []) {
echo 'Form is valid.';
}
Validation produces structured field errors instead of silently changing invalid data.
When validation fails, preserve useful input and show errors beside their fields. Escape values when they are inserted into HTML attributes or text.
<?php
$name = (string) ($_POST['name'] ?? '');
$safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
?>
<input name="name" value="<?= $safeName ?>">
ENT_QUOTES protects both quote styles so a submitted value cannot break out of the value attribute.
After saving valid data, redirect to a confirmation URL. The Post/Redirect/Get pattern prevents an accidental browser refresh from repeating the POST.
Any state-changing form also needs a server-verified CSRF token. The security lesson builds that token flow.
<?php
// Save validated data first.
header('Location: /profile/saved', true, 303);
exit;
A form handler begins by accepting only the intended HTTP method and media type. GET should retrieve a representation without changing server state; POST commonly submits a state-changing command. Method choice does not validate, encrypt, or authorize the values.
Read each expected field by name and reject unexpected shapes. Attackers can send arrays where a scalar was expected, omit browser-required fields, repeat keys, or bypass the HTML form entirely. Browser constraints improve interaction but never replace server validation.
`filter_input` reads the original value supplied by the server interface, before application changes to the matching superglobal. Use `filter_var` when validating a value that your code has already normalized. Always handle missing, invalid, and valid-falsy results distinctly.
Set limits before parsing: request bytes, field count, nesting depth, string length, and file size. Reject an unsupported content type or oversized request with a clear status rather than spending unbounded memory on it.
Normalization creates one representation for comparison, such as trimming permitted surrounding whitespace or converting an approved date format. Validation decides whether that representation belongs to the domain. Do not silently repair a value into a different user intention.
Required, type, range, length, format, and cross-field rules are different checks. Validate in a useful order so one field receives clear feedback. A password confirmation depends on two fields, while a booking end date depends on both dates and business policy.
Validation filters can recognize values such as email, integer, boolean, and URL forms, but acceptance still needs domain rules. A syntactically valid email may be too long for the schema; a valid URL may point to a forbidden host; integer zero may be a valid value rather than failure.
Keep submitted values separate from trusted domain values. Construct the domain command or value object only after all required checks pass. This prevents partially validated arrays from reaching persistence and authorization code.
Return errors keyed by stable field names and include a form-level error for failures that do not belong to one control. Preserve safe non-secret input so the learner can correct one field without retyping the form.
Render labels with explicit control associations, identify invalid controls accessibly, and connect each message through descriptive relationships. Move focus to a concise error summary when a failed submission reloads a long page, while keeping errors beside their fields.
Use `htmlspecialchars` with an explicit encoding and suitable quote handling when placing a value in HTML text or a quoted attribute. HTML escaping is not URL, JavaScript, CSS, SQL, CSV, or header escaping; each destination has its own safe construction method.
Never redisplay passwords, tokens, payment secrets, or hidden authorization values from submitted input. Hidden controls are user-controlled too. Derive identity and permission from the authenticated server session, not hidden fields.
Unchecked checkboxes usually send no key, while checked controls send their configured value. Radio groups submit one selected value. Multi-select controls and names ending in brackets can produce arrays. Define the expected shape for each control before reading it.
Never trust an option merely because it appeared in a select list. Validate submitted identifiers against the set allowed for the current user and current state. A stale or forged option can otherwise cross an authorization boundary.
Distinguish absent, empty, zero, and false. Null coalescing is convenient for optional text but can hide a required missing key. Parse booleans and integers with APIs that report invalid input rather than relying on a cast that transforms arbitrary text.
Dynamic repeated rows need stable identifiers and a maximum row count. Validate every row independently, reject duplicate identifiers when uniqueness matters, and map row-specific errors back to the same visible item.
A file form requires `multipart/form-data`; uploaded data appears in `$_FILES`, not as an ordinary POST string. Check the upload error code before using size, name, or temporary path because partial and missing uploads have different meanings.
Enforce server-side byte limits and inspect content with an appropriate detector. A browser-provided MIME type and filename are untrusted metadata. Generate a storage name and keep the original display name only after normalization and output encoding.
Move a verified upload with the upload-specific API into storage outside the executable public tree when possible. Restrict extensions, prevent path traversal, deny script execution, and apply image or document validation appropriate to the feature.
Uploads can consume disk, memory, and processing time. Set per-file and per-request quotas, clean abandoned temporary work, scan where the risk model requires it, and avoid synchronous expensive conversion without a bounded job policy.
Protect every state-changing browser form with an unpredictable token bound to the appropriate session and purpose. Compare the submitted token safely, expire or rotate it according to the application policy, and reject the request before performing side effects.
CSRF protection does not authorize the action. After authenticating the session, verify that this user may change this exact resource in its current state. Never accept an owner identifier from the form as proof of ownership.
After a successful POST, redirect with an appropriate status to a GET representation. Post/Redirect/Get prevents a normal refresh from repeating the browser POST, but it does not guarantee business-level idempotency under double clicks, retries, or concurrent requests.
For operations such as orders or payments, use an idempotency or unique-command key enforced by persistence. Disable repeated UI submission for usability, then rely on the server constraint for correctness.
Pass only validated domain values to persistence and bind SQL values through prepared statements. Database constraints remain the final guard for uniqueness, references, and concurrent changes. Translate constraint failures into a safe form-level or field-level result.
Wrap related writes in a transaction when they must succeed together. Roll back on exceptions, log a correlation identifier and diagnostic context without submitted secrets, and show the user a stable message that does not expose database details.
Test the initial GET, valid POST, every validation rule, absent fields, array-for-scalar attacks, oversized values, invalid encoding, stale options, CSRF failure, duplicate submission, persistence failure, and redirect destination. Assert that invalid requests cause no writes.
Render tests should verify escaped redisplay, selected and checked state, field associations, error summary links, and keyboard focus. Integration tests should use the real request parser and session middleware because hand-built arrays can miss boundary behavior.
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.