Form là cầu nối giữa người dùng và PHP. Bài này đi qua toàn bộ vòng đời xử lý form an toàn: nhận dữ liệu từ $_GET/$_POST, xác thực, làm sạch, hiển thị lỗi, giữ lại giá trị đã nhập, chống XSS/CSRF và mẫu PRG.
- Đọc dữ liệu từ $_GET, $_POST, $_REQUEST
- Xác thực (validate) và làm sạch (sanitize) dữ liệu
- Hiển thị lỗi từng trường và giữ giá trị đã nhập
- Áp dụng PRG, CSRF token, htmlspecialchars
- Validation
- Kiểm tra dữ liệu đúng quy tắc: bắt buộc, định dạng, độ dài.
- Sanitization
- Làm sạch: trim, bỏ thẻ, chuẩn hóa.
- PRG
- Post → Redirect → Get: chuyển hướng sau khi xử lý POST.
- CSRF
- Giả mạo yêu cầu từ trang khác; chống bằng token.
Khái niệm và lý thuyết
Khi người dùng bấm gửi, trình duyệt gửi các cặp name=giá trị; PHP đưa vào $_GET (method get) hoặc $_POST (method post). Nguyên tắc số một: không bao giờ tin dữ liệu từ người dùng. Kiểm tra ở JavaScript chỉ để tiện; kiểm tra ở PHP là bắt buộc.
| Bước | Việc làm | Hàm |
|---|---|---|
| 1. Nhận | Đọc với giá trị mặc định | $_POST["ten"] ?? "" |
| 2. Làm sạch | Bỏ khoảng trắng, chuẩn hóa | trim, mb_strtolower |
| 3. Xác thực | Bắt buộc, định dạng, miền giá trị | filter_var, preg_match, mb_strlen |
| 4. Xử lý | Lưu CSDL, gửi mail | prepared statement, password_hash |
| 5. Phản hồi | Chuyển hướng, báo kết quả | header("Location: ..."), session |
| Hiển thị | Luôn mã hóa đầu ra | htmlspecialchars |
GET: tìm kiếm
<form method="get" action="tim.php">
<input name="q" value="<?= htmlspecialchars($_GET['q'] ?? '') ?>" placeholder="Tìm sinh viên">
<button>Tìm</button>
</form>
<?php
$q = trim($_GET["q"] ?? "");
if ($q !== "") {
echo "<p>Kết quả cho: <b>" . htmlspecialchars($q) . "</b></p>";
// tìm trong CSDL (loạt PHP và MySQL)
}URL trở thành tim.php?q=An: có thể lưu, chia sẻ. GET dùng cho tìm kiếm, lọc, phân trang.
POST: form đăng ký hoàn chỉnh
<?php
// dang-ky.php
session_start();
$loi = []; $du_lieu = ["ten" => "", "email" => "", "lop" => ""];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// 1. Nhận + làm sạch
foreach ($du_lieu as $k => $v) $du_lieu[$k] = trim($_POST[$k] ?? "");
$mat_khau = $_POST["mat_khau"] ?? "";
// 2. Xác thực
if ($du_lieu["ten"] === "") $loi["ten"] = "Vui lòng nhập họ tên";
elseif (mb_strlen($du_lieu["ten"]) < 3) $loi["ten"] = "Họ tên quá ngắn";
if (!filter_var($du_lieu["email"], FILTER_VALIDATE_EMAIL)) $loi["email"] = "Email không hợp lệ";
if (!in_array($du_lieu["lop"], ["K65 Tin", "K66 Tin"], true)) $loi["lop"] = "Lớp không hợp lệ";
if (strlen($mat_khau) < 8) $loi["mat_khau"] = "Mật khẩu tối thiểu 8 ký tự";
if (!hash_equals($_SESSION["csrf"] ?? "", $_POST["csrf"] ?? "")) $loi["csrf"] = "Phiên không hợp lệ";
// 3. Xử lý + PRG
if (!$loi) {
$hash = password_hash($mat_khau, PASSWORD_DEFAULT); // không lưu mật khẩu thô
// lưu CSDL bằng prepared statement...
$_SESSION["thong_bao"] = "Đăng ký thành công!";
header("Location: dang-ky.php", true, 303);
exit;
}
}
$_SESSION["csrf"] = $_SESSION["csrf"] ?? bin2hex(random_bytes(16));
function e($s) { return htmlspecialchars($s, ENT_QUOTES, "UTF-8"); }
?>
<?php if (!empty($_SESSION["thong_bao"])): ?>
<p style="color:green"><?= e($_SESSION["thong_bao"]) ?></p>
<?php unset($_SESSION["thong_bao"]); ?>
<?php endif; ?>
<form method="post" novalidate>
<input type="hidden" name="csrf" value="<?= e($_SESSION["csrf"]) ?>">
<p><label>Họ tên <input name="ten" value="<?= e($du_lieu["ten"]) ?>"></label>
<small style="color:red"><?= e($loi["ten"] ?? "") ?></small></p>
<p><label>Email <input name="email" value="<?= e($du_lieu["email"]) ?>"></label>
<small style="color:red"><?= e($loi["email"] ?? "") ?></small></p>
<p><label>Lớp <select name="lop">
<?php foreach (["K65 Tin", "K66 Tin"] as $l): ?>
<option <?= $du_lieu["lop"] === $l ? "selected" : "" ?>><?= e($l) ?></option>
<?php endforeach; ?></select></label></p>
<p><label>Mật khẩu <input type="password" name="mat_khau"></label>
<small style="color:red"><?= e($loi["mat_khau"] ?? "") ?></small></p>
<button>Đăng ký</button>
</form>Mẫu trên gộp hiển thị và xử lý trong một tệp (self-processing form): dễ giữ lại giá trị đã nhập và hiện lỗi ngay cạnh ô. Sau khi thành công, chuyển hướng 303 để tải lại trang không gửi lại form.
Checkbox, radio, select nhiều
<?php
$so_thich = $_POST["so_thich"] ?? []; // name="so_thich[]" -> mảng
$so_thich = array_intersect($so_thich, ["doc", "the_thao", "nhac"]); // chỉ giữ giá trị hợp lệ
$dong_y = isset($_POST["dong_y"]); // checkbox không gửi khi không chọn
$he = $_POST["he"] ?? ""; // radio
$tuoi = filter_input(INPUT_POST, "tuoi", FILTER_VALIDATE_INT, ["options" => ["min_range" => 16, "max_range" => 60]]);
if ($tuoi === false) $loi["tuoi"] = "Tuổi từ 16 đến 60";Các hàm filter_var hữu ích
| Bộ lọc | Mục đích |
|---|---|
| FILTER_VALIDATE_EMAIL | Email hợp lệ |
| FILTER_VALIDATE_INT, _FLOAT | Số, có min/max |
| FILTER_VALIDATE_URL | Địa chỉ web |
| FILTER_VALIDATE_BOOLEAN | on/off, yes/no, 1/0 |
| FILTER_SANITIZE_NUMBER_INT | Giữ lại chữ số |
| FILTER_SANITIZE_SPECIAL_CHARS | Mã hóa ký tự đặc biệt |
Đọc kỹ loạt bài HTML - Form (6 bài chi tiết) để nắm phía giao diện; bài này là phía máy chủ tương ứng.
Không lưu mật khẩu dạng thô hay md5. Dùng password_hash khi lưu và password_verify khi đăng nhập.
- Hoàn thiện form đăng ký ở trên, thêm trường ngày sinh (kiểm tra ≥ 16 tuổi) và số điện thoại (preg_match).
- Viết form đăng nhập dùng password_verify với hash đã tạo sẵn.
- Làm form góp ý có textarea, giới hạn 500 ký tự, chống XSS khi hiển thị lại.
- Thử tấn công: nhập <script> vào ô tên và kiểm tra trang không bị chèn mã.
- $_GET cho tìm kiếm, $_POST cho dữ liệu nhạy cảm/thay đổi.
- Nhận → làm sạch → xác thực → xử lý → chuyển hướng (PRG).
- Hiện lỗi cạnh ô, giữ giá trị; htmlspecialchars mọi đầu ra.
- CSRF token, password_hash, filter_var là tiêu chuẩn tối thiểu.