๐ Lesson 11: Working with Forms ($_GET & $_POST)
Forms are the primary way users interact with web applications โ logging in, searching, registering, submitting orders. In this lesson, you'll learn how PHP receives and processes form data, the difference between GET and POST requests, and how to build forms that remember what the user typed.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Build HTML forms that submit data to PHP scripts
- Retrieve form data using the
$_GETand$_POSTsuperglobals - Explain when to use GET vs. POST and the implications of each
- Process various input types including text, checkboxes, radio buttons, selects, and textareas
- Handle multi-value inputs (checkboxes and multi-selects)
- Build sticky forms that preserve user input after submission
Estimated Time: 50 minutes
Prerequisites: Lessons 1โ10 (core PHP), basic HTML knowledge
๐ In This Lesson
How Forms Work with PHP
Until now, all the data in our PHP scripts has been hardcoded. Real web applications need to receive input from users โ and HTML forms are the standard way to do that. Here's the basic flow:
The Basics: An HTML Form That Talks to PHP
Here's the simplest possible form-to-PHP example. Two files working together:
File: greet_form.html โ the form page:
<!DOCTYPE html>
<html>
<head><title>Greeting Form</title></head>
<body>
<h1>Enter Your Name</h1>
<form action="greet.php" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<button type="submit">Greet Me</button>
</form>
</body>
</html>
File: greet.php โ the processing script:
<?php
// Read the "name" field from the submitted form
$name = $_POST["name"];
echo "<h1>Hello, $name!</h1>";
echo "<p>Welcome to our site.</p>";
Key Form Attributes
Every HTML form has two essential attributes that control where and how data is sent:
| Attribute | Purpose | Example |
|---|---|---|
action |
The URL of the PHP script that will process the form | action="process.php" |
method |
How data is sent: get or post |
method="post" |
โ ๏ธ The name Attribute Is Required!
Every input that should send data to PHP must have a name attribute. The id attribute is for CSS/JavaScript; PHP only sees name. If an input has no name, its value won't appear in $_GET or $_POST.
<!-- โ
PHP receives this โ has name="email" -->
<input type="email" name="email" id="email">
<!-- โ PHP does NOT receive this โ no name attribute -->
<input type="email" id="email">
GET vs. POST
The two HTTP methods you'll use for forms โ GET and POST โ behave very differently. Choosing the right one matters for security, usability, and correctness.
| Feature | GET | POST |
|---|---|---|
| Data location | URL query string (?name=value) |
Request body (hidden from URL) |
| Visible in URL? | Yes โ visible in address bar, browser history, server logs | No โ not visible in the URL |
| Bookmarkable? | Yes โ the full URL with data can be saved/shared | No โ bookmarking only saves the URL, not the data |
| Data size limit | ~2,000 characters (browser-dependent) | No practical limit (configurable on server) |
| Caching | Can be cached by browser | Never cached |
| Back button | Safe โ re-displays the page | Browser may warn "resubmit form data" |
| PHP superglobal | $_GET |
$_POST |
| Best for | Search, filtering, non-sensitive queries | Login, registration, data changes, sensitive data |
๐ Rule of Thumb
GET is for reading or looking up data โ search queries, filters, page navigation. The user should be able to bookmark the result.
POST is for writing or changing data โ creating accounts, submitting orders, updating profiles, anything with passwords or personal info.
GET in the URL
When a form uses method="get", the browser appends the data to the URL as a query string:
https://example.com/search.php?q=php+tutorials&category=web&page=1
โโโโโโโโโโโโ query string โโโโโโโโโโโโ
Each field becomes a key=value pair, separated by &. Spaces become + (or %20), and special characters are URL-encoded.
POST in the Body
When a form uses method="post", the same data is sent in the HTTP request body โ invisible to the user and not stored in browser history:
POST /register.php HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=alice&email=alice%40example.com&password=secret123
โ ๏ธ POST Is Not Encryption!
POST hides data from the URL bar, but the data is not encrypted. Anyone monitoring the network can read it. For truly secure transmission, you need HTTPS (which we cover in Lesson 23). POST just prevents the data from showing up in browser history and server access logs.
$_GET โ Query String Data
$_GET is a superglobal associative array that PHP automatically fills with data from the URL's query string. You don't need to parse the URL yourself โ PHP does it for you.
A Search Form Example
File: search_form.html
<form action="search.php" method="get">
<label for="q">Search:</label>
<input type="text" id="q" name="q" placeholder="Enter search term...">
<label for="category">Category:</label>
<select id="category" name="category">
<option value="all">All</option>
<option value="tutorials">Tutorials</option>
<option value="articles">Articles</option>
<option value="videos">Videos</option>
</select>
<button type="submit">Search</button>
</form>
File: search.php
<?php
// When the user submits: search.php?q=php+forms&category=tutorials
// Read individual values from $_GET
$query = $_GET["q"] ?? ""; // "php forms"
$category = $_GET["category"] ?? "all"; // "tutorials"
echo "<h1>Search Results</h1>";
echo "<p>Searching for: <strong>" . htmlspecialchars($query) . "</strong></p>";
echo "<p>Category: <strong>" . htmlspecialchars($category) . "</strong></p>";
// Check if any search was actually submitted
if ($query === "") {
echo "<p>Please enter a search term.</p>";
} else {
// In a real app, you'd query a database here
echo "<p>Showing results for <em>" . htmlspecialchars($query) . "</em>...</p>";
}
โ
Always Use htmlspecialchars() for Output!
Notice we wrap user input in htmlspecialchars() before echoing it. This prevents Cross-Site Scripting (XSS) attacks where malicious users inject HTML or JavaScript through form inputs. We'll cover this in depth in Lesson 23, but start the habit now:
// BAD โ vulnerable to XSS!
echo "Hello, " . $_GET["name"];
// GOOD โ safe output
echo "Hello, " . htmlspecialchars($_GET["name"] ?? "");
Checking if Data Exists
Users might visit your page directly (without submitting a form), so always check if the expected data exists:
<?php
// Method 1: Null coalescing operator (preferred)
$query = $_GET["q"] ?? "";
// Method 2: isset()
if (isset($_GET["q"])) {
$query = $_GET["q"];
} else {
$query = "";
}
// Method 3: empty() โ checks both existence AND emptiness
if (!empty($_GET["q"])) {
// Process the search
echo "Searching for: " . htmlspecialchars($_GET["q"]);
} else {
echo "No search term provided.";
}
// Method 4: array_key_exists() โ for when "" (empty string) is valid
if (array_key_exists("q", $_GET)) {
// Key exists, even if the value is ""
}
$_GET with Pagination Links
$_GET isn't only for forms โ any URL with a query string populates it. This is how pagination, filters, and sorting work:
<?php
// URL: products.php?page=3&sort=price&order=asc
$page = (int) ($_GET["page"] ?? 1);
$sort = $_GET["sort"] ?? "name";
$order = $_GET["order"] ?? "asc";
// Validate the values
$validSorts = ["name", "price", "date"];
$validOrders = ["asc", "desc"];
if (!in_array($sort, $validSorts)) {
$sort = "name";
}
if (!in_array($order, $validOrders)) {
$order = "asc";
}
echo "<p>Page $page, sorted by $sort ($order)</p>";
// Build pagination links
$prevPage = max(1, $page - 1);
$nextPage = $page + 1;
echo "<a href=\"products.php?page=$prevPage&sort=$sort&order=$order\">Previous</a> | ";
echo "<a href=\"products.php?page=$nextPage&sort=$sort&order=$order\">Next</a>";
$_POST โ Form Body Data
$_POST works exactly like $_GET, but it reads data sent in the request body instead of the URL. Use it for forms with method="post".
A Registration Form Example
File: register_form.html
<form action="register.php" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label for="confirm">Confirm Password:</label>
<input type="password" id="confirm" name="confirm" required>
<button type="submit">Register</button>
</form>
File: register.php
<?php
// Read POST data
$username = trim($_POST["username"] ?? "");
$email = trim($_POST["email"] ?? "");
$password = $_POST["password"] ?? "";
$confirm = $_POST["confirm"] ?? "";
// Basic validation
$errors = [];
if ($username === "") {
$errors[] = "Username is required.";
}
if ($email === "") {
$errors[] = "Email is required.";
}
if (strlen($password) < 8) {
$errors[] = "Password must be at least 8 characters.";
}
if ($password !== $confirm) {
$errors[] = "Passwords do not match.";
}
// Display results
if (count($errors) > 0) {
echo "<h2>Registration Failed</h2>";
echo "<ul>";
foreach ($errors as $error) {
echo "<li>" . htmlspecialchars($error) . "</li>";
}
echo "</ul>";
echo "<p><a href='register_form.html'>Go back and try again</a></p>";
} else {
echo "<h2>Registration Successful!</h2>";
echo "<p>Welcome, " . htmlspecialchars($username) . "!</p>";
// In a real app: hash password, save to database, start session
}
Detecting Form Submission
Sometimes you need to know whether the user actually submitted a form, or just arrived at the page. A common technique is to check the request method:
<?php
// Method 1: Check $_SERVER["REQUEST_METHOD"]
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// Form was submitted
$name = trim($_POST["name"] ?? "");
// ... process form
} else {
// Page loaded normally (GET request) โ show the form
}
// Method 2: Check for a specific form field
if (isset($_POST["username"])) {
// The registration form was submitted
}
// Method 3: Use a hidden field as a "form ID"
// In the HTML: <input type="hidden" name="form_action" value="register">
if (($_POST["form_action"] ?? "") === "register") {
// The registration form was submitted
}
โ
Prefer $_SERVER["REQUEST_METHOD"]
Checking the request method is the most reliable approach. It works even if the form has no fields filled in, and it clearly communicates intent in your code.
Processing Different Input Types
HTML offers many input types. Here's how each one sends data to PHP:
Text, Email, Password, Number
These all arrive as strings in $_POST or $_GET:
<?php
// HTML:
// <input type="text" name="city" value="Portland">
// <input type="email" name="email" value="a@b.com">
// <input type="number" name="age" value="30">
// <input type="password" name="pass" value="secret">
$city = $_POST["city"] ?? ""; // "Portland" (string)
$email = $_POST["email"] ?? ""; // "a@b.com" (string)
$age = (int) ($_POST["age"] ?? 0); // 30 (cast to int!)
$pass = $_POST["pass"] ?? ""; // "secret" (string)
โ ๏ธ Important: Eventype="number"sends a string to PHP. Always cast with(int)or(float)if you need a numeric type.
Textarea
<label for="bio">Bio:</label>
<textarea id="bio" name="bio" rows="5" cols="40"></textarea>
<?php
$bio = trim($_POST["bio"] ?? "");
// Contains the full text, including newlines (\n)
// To display in HTML, convert newlines to <br> tags
echo nl2br(htmlspecialchars($bio));
Radio Buttons
Radio buttons in a group share the same name. Only the selected one's value is sent:
<p>Preferred contact method:</p>
<label><input type="radio" name="contact" value="email"> Email</label>
<label><input type="radio" name="contact" value="phone"> Phone</label>
<label><input type="radio" name="contact" value="text"> Text</label>
<?php
$contact = $_POST["contact"] ?? "";
// "email", "phone", or "text" โ whichever was selected
// "" if nothing was selected (no default checked)
// Validate against known values
$validMethods = ["email", "phone", "text"];
if (!in_array($contact, $validMethods)) {
$contact = "email"; // Default fallback
}
Single Checkbox
A single checkbox is either sent or not. If checked, PHP receives its value. If unchecked, the key doesn't exist at all:
<label>
<input type="checkbox" name="agree" value="yes">
I agree to the terms and conditions
</label>
<?php
// If checked: $_POST["agree"] === "yes"
// If unchecked: "agree" key does NOT exist in $_POST
$agreed = isset($_POST["agree"]); // true or false
// OR
$agreed = ($_POST["agree"] ?? "") === "yes";
โ ๏ธ Unchecked Checkboxes Send Nothing
This is a common source of bugs. An unchecked checkbox doesn't send value="no" โ it sends nothing at all. The key won't exist in $_POST. Always use isset() or null coalescing to handle this.
Select (Dropdown)
<label for="country">Country:</label>
<select id="country" name="country">
<option value="">-- Select --</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
<option value="ph">Philippines</option>
</select>
<?php
$country = $_POST["country"] ?? "";
// "us", "ca", "uk", "ph", or "" (if "-- Select --" was left)
if ($country === "") {
echo "Please select a country.";
}
Hidden Inputs
Hidden inputs pass data that the user doesn't see but PHP needs:
<!-- Pass a product ID with the form -->
<input type="hidden" name="product_id" value="42">
<!-- Pass the page they came from -->
<input type="hidden" name="redirect_to" value="/dashboard">
<?php
$productId = (int) ($_POST["product_id"] ?? 0);
$redirectTo = $_POST["redirect_to"] ?? "/";
// โ ๏ธ Hidden fields can be edited by users (via browser dev tools)!
// Always validate hidden field values server-side.
Multi-Value Inputs
Some form controls let users select multiple values. To receive these as an array in PHP, you need to add [] to the name attribute.
Checkbox Groups
<p>Select your favorite languages:</p>
<label><input type="checkbox" name="languages[]" value="php"> PHP</label>
<label><input type="checkbox" name="languages[]" value="js"> JavaScript</label>
<label><input type="checkbox" name="languages[]" value="python"> Python</label>
<label><input type="checkbox" name="languages[]" value="csharp"> C#</label>
<label><input type="checkbox" name="languages[]" value="rust"> Rust</label>
<?php
// The [] in name="languages[]" tells PHP to expect an array
$languages = $_POST["languages"] ?? [];
// If user checked PHP, JavaScript, and Rust:
// $languages = ["php", "js", "rust"]
// If nothing was checked:
// "languages" key won't exist in $_POST, so we default to []
echo "You selected " . count($languages) . " language(s):\n";
foreach ($languages as $lang) {
echo " - " . htmlspecialchars($lang) . "\n";
}
// Validate each value
$validLanguages = ["php", "js", "python", "csharp", "rust"];
$languages = array_filter($languages, fn($l) => in_array($l, $validLanguages));
โ ๏ธ Don't Forget the [] Brackets!
Without [] in the name, PHP only receives the last checked value. With name="languages" (no brackets), checking PHP, JS, and Rust would only give you "rust".
<!-- BAD โ only sends last checked value -->
<input type="checkbox" name="languages" value="php">
<input type="checkbox" name="languages" value="js">
<!-- GOOD โ sends all checked values as an array -->
<input type="checkbox" name="languages[]" value="php">
<input type="checkbox" name="languages[]" value="js">
Multi-Select Dropdown
<label for="skills">Skills (hold Ctrl/Cmd to select multiple):</label>
<select id="skills" name="skills[]" multiple size="5">
<option value="html">HTML/CSS</option>
<option value="js">JavaScript</option>
<option value="php">PHP</option>
<option value="mysql">MySQL</option>
<option value="react">React</option>
<option value="node">Node.js</option>
</select>
<?php
$skills = $_POST["skills"] ?? [];
// Same as checkboxes โ an array of selected values
echo "Selected skills: " . implode(", ", array_map("htmlspecialchars", $skills));
Putting It All Together
Here's a complete example showing multiple input types in one form:
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$age = (int) ($_POST["age"] ?? 0);
$gender = $_POST["gender"] ?? "";
$languages = $_POST["languages"] ?? [];
$bio = trim($_POST["bio"] ?? "");
$newsletter = isset($_POST["newsletter"]);
echo "<h2>Profile Summary</h2>";
echo "<p><strong>Name:</strong> " . htmlspecialchars($name) . "</p>";
echo "<p><strong>Email:</strong> " . htmlspecialchars($email) . "</p>";
echo "<p><strong>Age:</strong> $age</p>";
echo "<p><strong>Gender:</strong> " . htmlspecialchars($gender) . "</p>";
echo "<p><strong>Languages:</strong> " . implode(", ", array_map("htmlspecialchars", $languages)) . "</p>";
echo "<p><strong>Bio:</strong> " . nl2br(htmlspecialchars($bio)) . "</p>";
echo "<p><strong>Newsletter:</strong> " . ($newsletter ? "Yes" : "No") . "</p>";
}
Sticky Forms
Imagine filling out a long registration form, making one mistake, and having every field reset to blank. Frustrating! Sticky forms preserve the user's input so they only need to fix what was wrong.
๐ Definition
Sticky Form: A form that remembers and redisplays the values the user previously entered after a submission that resulted in errors or needed corrections.
Making Text Inputs Sticky
Set the value attribute to the previously submitted value:
<?php
$name = "";
$email = "";
$errors = [];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
if ($name === "") {
$errors[] = "Name is required.";
}
if ($email === "" || !str_contains($email, "@")) {
$errors[] = "Valid email is required.";
}
if (empty($errors)) {
// Success! Process the data and redirect
echo "<p>Thank you, " . htmlspecialchars($name) . "!</p>";
exit;
}
}
?>
<!-- Display errors if any -->
<?php if (!empty($errors)): ?>
<div class="errors">
<ul>
<?php foreach ($errors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<!-- The form โ values are "sticky" -->
<form method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name"
value="<?= htmlspecialchars($name) ?>">
<label for="email">Email:</label>
<input type="email" id="email" name="email"
value="<?= htmlspecialchars($email) ?>">
<button type="submit">Submit</button>
</form>
๐ก Key Insight: Notice howhtmlspecialchars()is used in thevalueattribute. This isn't just for display โ it prevents a user from injecting HTML into your form by entering something like"><script>alert('hacked')</script>as their name.
Making Textareas Sticky
Textareas don't use a value attribute โ the content goes between the tags:
<label for="bio">Bio:</label>
<textarea id="bio" name="bio" rows="5"><?= htmlspecialchars($bio) ?></textarea>
<!-- โ ๏ธ No space between > and <?= or you'll get extra whitespace! -->
Making Radio Buttons Sticky
Add the checked attribute to the previously selected option:
<?php $contact = $_POST["contact"] ?? "email"; ?>
<label>
<input type="radio" name="contact" value="email"
<?= $contact === "email" ? "checked" : "" ?>>
Email
</label>
<label>
<input type="radio" name="contact" value="phone"
<?= $contact === "phone" ? "checked" : "" ?>>
Phone
</label>
<label>
<input type="radio" name="contact" value="text"
<?= $contact === "text" ? "checked" : "" ?>>
Text
</label>
Making Checkboxes Sticky
<?php $languages = $_POST["languages"] ?? []; ?>
<label>
<input type="checkbox" name="languages[]" value="php"
<?= in_array("php", $languages) ? "checked" : "" ?>>
PHP
</label>
<label>
<input type="checkbox" name="languages[]" value="js"
<?= in_array("js", $languages) ? "checked" : "" ?>>
JavaScript
</label>
<label>
<input type="checkbox" name="languages[]" value="python"
<?= in_array("python", $languages) ? "checked" : "" ?>>
Python
</label>
Making Select Dropdowns Sticky
<?php $country = $_POST["country"] ?? ""; ?>
<select name="country">
<option value="">-- Select --</option>
<option value="us" <?= $country === "us" ? "selected" : "" ?>>United States</option>
<option value="ca" <?= $country === "ca" ? "selected" : "" ?>>Canada</option>
<option value="uk" <?= $country === "uk" ? "selected" : "" ?>>United Kingdom</option>
<option value="ph" <?= $country === "ph" ? "selected" : "" ?>>Philippines</option>
</select>
โ Sticky Form Cheat Sheet
| Input Type | How to Make Sticky |
|---|---|
| Text / Email / Password / Number | value="<?= htmlspecialchars($var) ?>" |
| Textarea | Content between tags: ><?= htmlspecialchars($var) ?></textarea> |
| Radio | <?= $var === "value" ? "checked" : "" ?> |
| Checkbox (single) | <?= isset($_POST["key"]) ? "checked" : "" ?> |
| Checkbox (group) | <?= in_array("value", $arr) ? "checked" : "" ?> |
| Select | <?= $var === "value" ? "selected" : "" ?> |
Self-Processing Forms
In the examples so far, the form and the processing script were separate files. In practice, it's common to combine them into a single PHP file that both displays the form and processes it. This is called a self-processing (or self-submitting) form.
The Pattern
<?php
// --- Step 1: Initialize variables ---
$name = "";
$email = "";
$message = "";
$errors = [];
$success = false;
// --- Step 2: Process form if submitted ---
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// Read and trim input
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$message = trim($_POST["message"] ?? "");
// Validate
if ($name === "") {
$errors[] = "Name is required.";
}
if ($email === "" || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "A valid email is required.";
}
if ($message === "") {
$errors[] = "Message is required.";
}
// If no errors, process the data
if (empty($errors)) {
// Save to database, send email, etc.
$success = true;
}
}
?>
<!DOCTYPE html>
<html>
<head><title>Contact Us</title></head>
<body>
<h1>Contact Us</h1>
<?php if ($success): ?>
<div class="success">
<p>Thank you, <?= htmlspecialchars($name) ?>! Your message has been sent.</p>
</div>
<?php else: ?>
<?php if (!empty($errors)): ?>
<div class="errors">
<ul>
<?php foreach ($errors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="post" action="">
<label for="name">Name:</label>
<input type="text" id="name" name="name"
value="<?= htmlspecialchars($name) ?>">
<label for="email">Email:</label>
<input type="email" id="email" name="email"
value="<?= htmlspecialchars($email) ?>">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="5"><?= htmlspecialchars($message) ?></textarea>
<button type="submit">Send Message</button>
</form>
<?php endif; ?>
</body>
</html>
Why action=""?
Setting action="" (empty string) makes the form submit to the same URL โ the current page. You can also use action="<?= htmlspecialchars($_SERVER["PHP_SELF"]) ?>", but the empty string is simpler and just as effective.
โ ๏ธ Don't Use $_SERVER["PHP_SELF"] Unescaped!
If you do use PHP_SELF, always wrap it in htmlspecialchars(). An attacker could craft a URL like /contact.php/"><script>alert('xss')</script> to inject code into your form's action attribute.
<!-- BAD โ XSS vulnerability -->
<form action="<?= $_SERVER["PHP_SELF"] ?>" method="post">
<!-- GOOD โ escaped -->
<form action="<?= htmlspecialchars($_SERVER["PHP_SELF"]) ?>" method="post">
<!-- BEST โ just use empty action -->
<form action="" method="post">
Post/Redirect/Get (PRG) Pattern
After a successful POST form submission, if the user refreshes the page, the browser will ask "Resubmit form data?" and may duplicate the action (double order, double message, etc.). The PRG pattern prevents this:
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"] ?? "");
// ... validate ...
if (empty($errors)) {
// Process the data (save to DB, send email, etc.)
// Redirect to prevent resubmission on refresh
header("Location: contact.php?success=1");
exit; // Always exit after header redirect!
}
}
// Check for success message from redirect
if (isset($_GET["success"])) {
echo "<p class='success'>Your message has been sent!</p>";
}
// ... show form ...
โ PRG is a Best Practice
The Post/Redirect/Get pattern is used by virtually every professional web application. After processing a POST request successfully, redirect the user with header("Location: ...") followed by exit. The redirect converts the POST into a GET, so refreshing the page won't resubmit the form.
Hands-On Exercises
๐๏ธ Exercise 1: Calculator Form
Objective: Build a self-processing calculator that takes two numbers and an operation.
Instructions:
- Create a form with two number inputs and a select dropdown for the operation (+, -, *, /)
- Use
method="post"and make the form self-processing - Display the result below the form after submission
- Make the form sticky โ preserve the inputs after submission
- Handle division by zero with an error message
๐ก Hint
Use a match expression for the operation. Cast inputs to (float). Check for $num2 == 0 before dividing.
โ Solution
<?php
$num1 = "";
$num2 = "";
$op = "+";
$result = null;
$error = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$num1 = $_POST["num1"] ?? "";
$num2 = $_POST["num2"] ?? "";
$op = $_POST["op"] ?? "+";
if ($num1 === "" || $num2 === "" || !is_numeric($num1) || !is_numeric($num2)) {
$error = "Please enter valid numbers.";
} elseif ($op === "/" && (float)$num2 == 0) {
$error = "Cannot divide by zero!";
} else {
$a = (float) $num1;
$b = (float) $num2;
$result = match ($op) {
"+" => $a + $b,
"-" => $a - $b,
"*" => $a * $b,
"/" => $a / $b,
default => null,
};
}
}
?>
<h2>Calculator</h2>
<?php if ($error): ?>
<p style="color: red;"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<form method="post" action="">
<input type="number" name="num1" step="any"
value="<?= htmlspecialchars($num1) ?>" placeholder="First number">
<select name="op">
<option value="+" <?= $op === "+" ? "selected" : "" ?>>+</option>
<option value="-" <?= $op === "-" ? "selected" : "" ?>>-</option>
<option value="*" <?= $op === "*" ? "selected" : "" ?>>ร</option>
<option value="/" <?= $op === "/" ? "selected" : "" ?>>รท</option>
</select>
<input type="number" name="num2" step="any"
value="<?= htmlspecialchars($num2) ?>" placeholder="Second number">
<button type="submit">Calculate</button>
</form>
<?php if ($result !== null): ?>
<p><strong>Result:</strong>
<?= htmlspecialchars($num1) ?> <?= htmlspecialchars($op) ?>
<?= htmlspecialchars($num2) ?> = <strong><?= $result ?></strong></p>
<?php endif; ?>
๐๏ธ Exercise 2: Survey Form with All Input Types
Objective: Build a complete survey form that uses every input type covered in this lesson.
Instructions:
- Create a self-processing survey with these fields:
- Name (text)
- Email (email)
- Age (number)
- Gender (radio buttons)
- Favorite colors (checkbox group โ at least 5 options)
- Country (select dropdown)
- Comments (textarea)
- Subscribe to newsletter (single checkbox)
- Validate: name and email are required, age must be 13โ120
- Make every field sticky
- On success, display a formatted summary of all responses
๐ก Hint
Initialize all variables at the top. Use in_array() for sticky checkboxes and radios. Remember the [] brackets for checkbox groups.
โ Solution
<?php
$name = "";
$email = "";
$age = "";
$gender = "";
$colors = [];
$country = "";
$comments = "";
$newsletter = false;
$errors = [];
$success = false;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$age = $_POST["age"] ?? "";
$gender = $_POST["gender"] ?? "";
$colors = $_POST["colors"] ?? [];
$country = $_POST["country"] ?? "";
$comments = trim($_POST["comments"] ?? "");
$newsletter = isset($_POST["newsletter"]);
// Validate
if ($name === "") { $errors[] = "Name is required."; }
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $errors[] = "Valid email is required."; }
if ($age === "" || (int)$age < 13 || (int)$age > 120) { $errors[] = "Age must be 13โ120."; }
// Validate multi-values
$validColors = ["red", "blue", "green", "yellow", "purple", "orange"];
$colors = array_filter($colors, fn($c) => in_array($c, $validColors));
if (empty($errors)) { $success = true; }
}
?>
<?php if ($success): ?>
<h2>Survey Summary</h2>
<p><strong>Name:</strong> <?= htmlspecialchars($name) ?></p>
<p><strong>Email:</strong> <?= htmlspecialchars($email) ?></p>
<p><strong>Age:</strong> <?= (int)$age ?></p>
<p><strong>Gender:</strong> <?= htmlspecialchars($gender ?: "Not specified") ?></p>
<p><strong>Colors:</strong> <?= implode(", ", array_map("htmlspecialchars", $colors)) ?: "None" ?></p>
<p><strong>Country:</strong> <?= htmlspecialchars($country ?: "Not specified") ?></p>
<p><strong>Comments:</strong> <?= nl2br(htmlspecialchars($comments)) ?: "None" ?></p>
<p><strong>Newsletter:</strong> <?= $newsletter ? "Yes" : "No" ?></p>
<?php else: ?>
<?php if (!empty($errors)): ?>
<div style="color:red"><ul>
<?php foreach ($errors as $e): ?>
<li><?= htmlspecialchars($e) ?></li>
<?php endforeach; ?>
</ul></div>
<?php endif; ?>
<form method="post" action="">
<label>Name: <input type="text" name="name" value="<?= htmlspecialchars($name) ?>"></label><br>
<label>Email: <input type="email" name="email" value="<?= htmlspecialchars($email) ?>"></label><br>
<label>Age: <input type="number" name="age" min="13" max="120" value="<?= htmlspecialchars($age) ?>"></label><br>
<p>Gender:</p>
<label><input type="radio" name="gender" value="male" <?= $gender === "male" ? "checked" : "" ?>> Male</label>
<label><input type="radio" name="gender" value="female" <?= $gender === "female" ? "checked" : "" ?>> Female</label>
<label><input type="radio" name="gender" value="other" <?= $gender === "other" ? "checked" : "" ?>> Other</label>
<label><input type="radio" name="gender" value="prefer_not" <?= $gender === "prefer_not" ? "checked" : "" ?>> Prefer not to say</label><br>
<p>Favorite Colors:</p>
<?php foreach (["red","blue","green","yellow","purple","orange"] as $c): ?>
<label><input type="checkbox" name="colors[]" value="<?= $c ?>"
<?= in_array($c, $colors) ? "checked" : "" ?>> <?= ucfirst($c) ?></label>
<?php endforeach; ?><br>
<label>Country:
<select name="country">
<option value="">-- Select --</option>
<?php foreach (["us"=>"United States","ca"=>"Canada","uk"=>"UK","ph"=>"Philippines"] as $code => $label): ?>
<option value="<?= $code ?>" <?= $country === $code ? "selected" : "" ?>><?= $label ?></option>
<?php endforeach; ?>
</select></label><br>
<label>Comments:<br><textarea name="comments" rows="4" cols="40"><?= htmlspecialchars($comments) ?></textarea></label><br>
<label><input type="checkbox" name="newsletter" <?= $newsletter ? "checked" : "" ?>> Subscribe to newsletter</label><br>
<button type="submit">Submit Survey</button>
</form>
<?php endif; ?>
๐ฏ Quick Quiz
Question 1: Which HTTP method should you use for a login form?
Question 2: What happens when an unchecked checkbox is submitted?
Question 3: Why do checkbox groups need [] in the name attribute?
Question 4: What does htmlspecialchars() do when used in a form's value attribute?
Question 5: What is the Post/Redirect/Get (PRG) pattern?
Summary
๐ Key Takeaways
- HTML forms send data to PHP via the
actionandmethodattributes; every input needs anameattribute - GET sends data in the URL (bookmarkable, visible) โ use for searches, filters, navigation
- POST sends data in the request body (hidden from URL) โ use for sensitive data, creating/updating resources
$_GETand$_POSTare superglobal arrays that PHP fills automatically with submitted data- Always check if data exists with
??,isset(), or!empty()before using it - Always use
htmlspecialchars()when outputting user data to prevent XSS - Multi-value inputs (checkbox groups, multi-selects) need
[]in the name:name="items[]" - Sticky forms preserve user input by echoing values back into form fields with proper escaping
- Self-processing forms combine the form and handler in one file using
$_SERVER["REQUEST_METHOD"] - PRG pattern โ redirect after successful POST to prevent duplicate submissions on refresh
๐ Additional Resources
- PHP Manual: $_GET
- PHP Manual: $_POST
- PHP Manual: $_SERVER
- PHP Manual: htmlspecialchars()
- MDN: Web Forms Guide
- Wikipedia: Post/Redirect/Get Pattern
๐ What's Next?
Now that you can receive user input, you need to make sure it's safe and valid. In Lesson 12: Input Validation & Sanitization, you'll learn to use filter_var(), filter_input(), PHP's built-in validation filters, regular expressions for custom rules, and how to display meaningful error messages.
๐ Congratulations!
You've unlocked PHP's connection to the browser! From here on, everything you build will involve user interaction.