Forms

By the end of this lesson you'll be able to read form data from $_GET and $_POST , sanitize and validate it, re-display it safely, handle file uploads, and protect your forms with CSRF tokens — the security skills every PHP app depends on.

Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Where Form Data Arrives: $ _GET, $ _POST, $ _REQUEST

When a visitor submits a form, PHP hands you the data in a superglobal — a built-in array you can read from anywhere. A form with method="get" puts its data in the URL and you read it from $_GET ; a form with method="post" sends it in the hidden request body and you read it from $_POST . Use GET for harmless reads (a search, a page number) where a shareable URL helps, and POST for anything sensitive or state-changing (logins, sign-ups, comments). There's also $_REQUEST , which merges both — convenient, but it hides where the data came from, so prefer the specific one.

2️⃣ Sanitize: Make Input Safe with htmlspecialchars

Never trust user input. If you echo what someone typed straight back onto the page and they typed < script > , that script runs in your other visitors' browsers — that's an XSS (cross-site scripting) attack. The fix is to escape the output: htmlspecialchars() converts the dangerous characters < > " ' & into harmless HTML entities, so the browser shows the tag as text instead of running it. Pair it with trim() to drop stray whitespace, and the ?? operator to supply a fallback when a field is missing.

See how <script> became <script> in the safe version? Those entities display as literal angle brackets — the browser will never execute them. Pass ENT_QUOTES so both single and double quotes are escaped too, which matters the moment you put a value inside an HTML attribute.

3️⃣ Validate: Check Input Is Correct with filter_var

Sanitizing makes input safe to display ; validating checks it's actually correct — that an email is a real email and an age is a number in range. PHP's filter_var() does this for common types without you writing any regex: FILTER_VALIDATE_EMAIL , FILTER_VALIDATE_INT (with optional min_range / max_range ), FILTER_VALIDATE_URL and more. It returns the cleaned value on success or false on failure. For required fields , the simplest rule is "must not be empty after trim() ". Collect every problem into an $errors array so you can report them all at once.

4️⃣ Re-Display Values Safely (Sticky Forms)

When validation fails you should send the form back with the user's answers still in the boxes — a sticky form — so they don't retype everything. The trap: you're putting user data back into HTML, so the same XSS risk returns. The rule is absolute — escape every value before it re-enters the page , including values you place inside value="..." attributes. A one-line safe() helper that wraps htmlspecialchars() means you can never forget.

The double quote in Bob "the builder" became " , so it can't close the value="..." attribute early and inject new HTML. That single substitution is the difference between a working form and a hijacked one.

5️⃣ File Uploads with $ _FILES

Uploaded files don't appear in $_POST — they arrive in $_FILES , and only if the form uses method="post" with enctype="multipart/form-data" . PHP saves the upload to a temporary path and gives you its name , type , tmp_name , error and size . Validate all of it: check error === UPLOAD_ERR_OK , cap the size , and whitelist the type. Then move_uploaded_file() moves it out of the temp folder. Crucially, generate your own filename — never reuse the user's, which could be ../../evil.php .

Now you try. The script below is almost complete — fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

One more, this time about safe output. A user submitted HTML in a comment — escape it so it can't run.

6️⃣ Protect Forms with a CSRF Token

CSRF (cross-site request forgery) is a sneaky attack: a malicious page makes a logged-in user's browser submit your form without them realising — and because the browser automatically attaches their session cookie, your server thinks it's a genuine request. The defence is a CSRF token : a long random secret you store in the user's session and embed as a hidden field in every state-changing form. When the form comes back, you compare the submitted token to the stored one with hash_equals() (a timing-safe comparison — never use == for secrets). An attacker can't read or guess the token, so forged submissions are rejected.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Forms & Input

Tool

Example

What It Does

$ _GET / $ _POST

$ _POST["name"]

Read submitted form data

??

$ _POST["x"] ?? ""

Fallback if the key is missing

trim()

trim(" hi ")

Strip surrounding whitespace

htmlspecialchars()

htmlspecialchars($s, ENT_QUOTES)

Escape HTML to stop XSS

filter_var()

filter_var($e, FILTER_VALIDATE_EMAIL)

Validate email/int/URL/IP

$ _FILES

$ _FILES["avatar"]["tmp_name"]

Access an uploaded file

move_uploaded_file()

move_uploaded_file($tmp, $dest)

Save an upload safely

hash_equals()

hash_equals($a, $b)

Timing-safe token compare (CSRF)

Frequently Asked Questions

Mini-Challenge: A Contact-Form Handler

No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This is the read-the-input, validate, escape, respond loop behind every real form.

Lesson Complete!

Practice quiz

Which superglobal holds data from a form submitted with method="post"?

  • $_GET
  • $_SERVER
  • $_POST
  • $_COOKIE

Answer: $_POST. A method="post" form sends data in the request body, which PHP exposes in $_POST. method="get" data lands in $_GET.

What does the ?? operator do when reading $_POST["name"] ?? ""?

  • Supplies a fallback if the key is missing, avoiding a warning
  • Validates the value
  • Escapes the value
  • Forces it to a string type

Answer: Supplies a fallback if the key is missing, avoiding a warning. The null coalescing operator ?? gives a fallback when the key is absent, so you never hit an 'Undefined array key' warning.

What is the difference between sanitizing and validating input?

  • They are the same thing
  • Validating escapes HTML; sanitizing checks ranges
  • Both only run on the client
  • Sanitizing makes input safe to use/display; validating checks it is correct

Answer: Sanitizing makes input safe to use/display; validating checks it is correct. Sanitizing (e.g. htmlspecialchars) makes input safe; validating (e.g. filter_var) confirms it is correct. You usually do both.

Which function escapes < > " ' & so user input can't run as HTML (stopping XSS)?

  • strip_tags()
  • htmlspecialchars()
  • trim()
  • filter_var()

Answer: htmlspecialchars(). htmlspecialchars() turns those characters into harmless entities so the browser displays the tag as text instead of running it.

Why pass ENT_QUOTES to htmlspecialchars when putting a value inside value="..."?

  • It escapes both single and double quotes so the value can't break out of the attribute
  • It speeds up escaping
  • It validates the email
  • It removes whitespace

Answer: It escapes both single and double quotes so the value can't break out of the attribute. ENT_QUOTES escapes both quote types, so a double quote becomes &quot; and can't close the value="..." attribute early.

What does filter_var($email, FILTER_VALIDATE_EMAIL) return for an invalid email?

  • An empty string
  • null
  • false
  • The original string

Answer: false. filter_var returns the cleaned value on success or false on failure, so === false signals an invalid email.

Why isn't client-side (JavaScript) validation enough on its own?

  • It is too slow
  • Users can disable JS or post directly with curl, bypassing it — the server must re-check
  • It can't validate emails
  • It only works in Chrome

Answer: Users can disable JS or post directly with curl, bypassing it — the server must re-check. The user controls the client. JS validation is just UX; every rule that matters must be enforced again on the server.

Where do uploaded files arrive in PHP?

  • $_POST
  • $_GET
  • $_SESSION
  • $_FILES

Answer: $_FILES. Uploads arrive in $_FILES (with form enctype="multipart/form-data"), giving name, type, tmp_name, error and size.

Which function should compare a submitted CSRF token to the stored one?

  • ==
  • hash_equals()
  • ===
  • strcmp()

Answer: hash_equals(). hash_equals() is a timing-safe comparison for secrets. Never use == for token comparison.

For a stored file upload, what name should you save it under?

  • The user's original filename
  • The browser-supplied type
  • A name you generate yourself (e.g. random bytes)
  • Always 'upload.tmp'

Answer: A name you generate yourself (e.g. random bytes). Never reuse the user's filename (it could be ../../evil.php). Generate your own name and move it with move_uploaded_file().

Continue this course

Related lessons