$_GET vs $_POST
| Aspect | GET | POST |
|---|---|---|
| Data location | URL query string | Request body |
| Visible in URL | Yes | No |
| Cacheable | Yes | No |
| Bookmarkable | Yes | No |
| Max size | ~2KB (URL limit) | Usually 8MB+ (post_max_size) |
| Use for | Reads, searches, filters | Writes, logins, forms |
Reading Form Data Safely
<?php
// Detect HTTP method
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
showForm();
exit;
}
// Always default missing fields - prevents "undefined index" warnings
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$message = trim($_POST["message"] ?? "");
// Read URL query (GET)
$page = (int) ($_GET["page"] ?? 1);
$search = trim($_GET["q"] ?? "");
Validation with filter_var
<?php
$errors = [];
if ($name === "") {
$errors["name"] = "Name is required";
} elseif (mb_strlen($name) > 100) {
$errors["name"] = "Name too long";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "Invalid email address";
}
$url = filter_var($_POST["website"] ?? "", FILTER_VALIDATE_URL);
if ($url === false) {
$errors["website"] = "Invalid URL";
}
$age = filter_var($_POST["age"] ?? "", FILTER_VALIDATE_INT, [
"options" => ["min_range" => 13, "max_range" => 120],
]);
if ($age === false) {
$errors["age"] = "Age must be between 13 and 120";
}
if (!empty($errors)) {
showForm($errors);
exit;
}
Sanitization & Escaping
<?php
// HTML output - escape with htmlspecialchars
echo "Hello, " . htmlspecialchars($name, ENT_QUOTES, "UTF-8");
// URL building - urlencode
header("Location: /search?q=" . urlencode($search));
// SQL - NEVER concatenate. Use prepared statements:
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$name, $email]);
// JSON output - json_encode handles escaping
header("Content-Type: application/json");
echo json_encode(["name" => $name]);
Different output contexts need different escaping: HTML = htmlspecialchars(), HTML attribute = same plus ENT_QUOTES, URL = urlencode(), JS string = json_encode(), SQL = prepared statements. One-size-fits-all escaping is unsafe.
Full Contact Form Example
<?php
session_start();
$errors = [];
$values = ["name" => "", "email" => "", "message" => ""];
$saved = false;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// 1. Read with defaults
$values["name"] = trim($_POST["name"] ?? "");
$values["email"] = trim($_POST["email"] ?? "");
$values["message"] = trim($_POST["message"] ?? "");
// 2. CSRF check
if (!hash_equals($_SESSION["csrf"] ?? "", $_POST["csrf"] ?? "")) {
$errors["form"] = "Invalid form token. Please try again.";
}
// 3. Validate
if ($values["name"] === "") $errors["name"] = "Name required";
if (!filter_var($values["email"], FILTER_VALIDATE_EMAIL))
$errors["email"] = "Invalid email";
if (mb_strlen($values["message"]) < 10)
$errors["message"] = "Message too short";
// 4. Process
if (!$errors) {
// ... save to DB or send email
$saved = true;
$values = ["name" => "", "email" => "", "message" => ""];
}
}
// Always generate a fresh token
$_SESSION["csrf"] = bin2hex(random_bytes(32));
?>
<form method="post">
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION["csrf"]) ?>">
<input name="name" value="<?= htmlspecialchars($values["name"]) ?>">
<?php if (isset($errors["name"])): ?><small><?= $errors["name"] ?></small><?php endif; ?>
<input name="email" value="<?= htmlspecialchars($values["email"]) ?>">
<textarea name="message"><?= htmlspecialchars($values["message"]) ?></textarea>
<button>Send</button>
</form>
File Uploads
<?php
// HTML: <form enctype="multipart/form-data" method="post">
// <input type="file" name="avatar">
// </form>
if (!isset($_FILES["avatar"]) || $_FILES["avatar"]["error"] !== UPLOAD_ERR_OK) {
die("Upload failed");
}
$file = $_FILES["avatar"];
// Check size (also enforce in php.ini upload_max_filesize)
if ($file["size"] > 2 * 1024 * 1024) die("File too large (max 2MB)");
// Verify MIME type by content, NOT by filename
$mime = mime_content_type($file["tmp_name"]);
$allowed = ["image/jpeg" => "jpg", "image/png" => "png", "image/webp" => "webp"];
if (!isset($allowed[$mime])) die("Only JPG, PNG, WebP allowed");
// Generate safe filename - never trust user-supplied name
$ext = $allowed[$mime];
$name = bin2hex(random_bytes(16)) . ".$ext";
$dest = __DIR__ . "/uploads/$name";
if (!move_uploaded_file($file["tmp_name"], $dest)) {
die("Failed to save upload");
}
CSRF Protection
<?php
session_start();
function csrfToken(): string {
if (empty($_SESSION["csrf"])) {
$_SESSION["csrf"] = bin2hex(random_bytes(32));
}
return $_SESSION["csrf"];
}
function csrfCheck(): void {
$posted = $_POST["csrf"] ?? "";
$stored = $_SESSION["csrf"] ?? "";
// Use hash_equals to prevent timing attacks
if (!hash_equals($stored, $posted)) {
http_response_code(403);
die("CSRF token mismatch");
}
}
// In the form
?>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrfToken() ?>">
...
</form>
// In the handler
<?php csrfCheck(); ?>