Một form PHP gửi dữ liệu về chính script đã in ra nó: viết <form method="post"> với các ô nhập có thuộc tính name, và khi $_SERVER['REQUEST_METHOD'] là 'POST', đọc từng trường bằng $_POST['name']. Escape mọi thứ bạn in ngược lại bằng htmlspecialchars().
Chạy khối code này, gõ một cái tên vào form rồi nhấn Send: cùng script đó chạy lại, lần này $_POST đã có dữ liệu.
Ở đây có ba việc mà mọi đoạn xử lý form đều cần:
- Kiểm tra phương thức. Ở lần truy cập đầu, request là GET và chưa có gì để xử lý.
?? 'GET'chỉ có tác dụng khi tệp chạy từ dòng lệnh, nơiREQUEST_METHODkhông được gán; trên máy chủ web thì biến này luôn có. ?? ''cho mọi trường. Một trường có thể bị thiếu (ô nhập bị đổi tên, request do script gửi), và đọc một khóa không tồn tại sẽ in ra cảnh báo.- Escape khi xuất ra. Cái tên được đưa trở lại trang hai lần, và
htmlspecialchars()bảo đảm một giá trị như<b>Ada</b>hiện ra dưới dạng chữ thay vì thành mã HTML. Chi tiết có ở trang htmlspecialchars.
Kiểm tra form và hiện lỗi cạnh từng trường
Gom lỗi vào một mảng có khóa là tên trường. Nếu mảng rỗng, form hợp lệ; nếu không, in lại form với từng thông báo nằm dưới trường tương ứng và giữ nguyên dữ liệu người truy cập đã nhập. Hãy gửi form khi để trống, rồi với email sai, rồi với giá trị hợp lệ.
novalidate tắt phần kiểm tra sẵn có của trình duyệt để bạn thấy phần kiểm tra của PHP hoạt động. Trong form thật, hãy giữ cả hai: kiểm tra phía trình duyệt là tiện ích cho người truy cập, còn kiểm tra bằng PHP mới là thứ quyết định, vì ai cũng có thể gửi request mà không cần form của bạn. filter_var() xử lý email và khoảng giá trị số nguyên, mỗi việc một lần gọi; trang filter_var nói về các bộ lọc khác của hàm này.
Checkbox, radio button và select
Một ô văn bản luôn gửi giá trị, kể cả giá trị rỗng. Các loại điều khiển khác thì không:
- Checkbox không được chọn sẽ không gửi gì cả, nên khóa của nó không có trong
$_POST. - Các checkbox tên
topics[]đến dưới dạng mảng:$_POST['topics']là['php', 'sql']. - Một nhóm radio hoặc một
<select>gửivaluecủa lựa chọn được chọn.
Để giữ lựa chọn sau khi submit, in checked hoặc selected lên lựa chọn khớp. Tích vài ô, chọn một cấp độ rồi nhấn Save.
Hai phép kiểm tra sau khi đọc đầu vào quan trọng hơn vẻ ngoài của chúng. array_intersect() loại bỏ mọi chủ đề không có trong danh sách của bạn, còn phép kiểm tra is_string() cùng array_key_exists() đưa cấp độ về giá trị mặc định nếu ai đó gửi một giá trị bạn chưa từng đưa ra (hoặc một mảng, thứ sẽ khiến $levels[$level] ném lỗi). Select giới hạn những gì trình duyệt có thể gửi, chứ không giới hạn những gì một request có thể chứa.
Form nhiều bước với trường ẩn
Một input ẩn mang giá trị từ bước này sang bước kế tiếp mà không hiển thị ra. Ở đây bước 1 hỏi tên, bước 2 hỏi ngôn ngữ và gửi kèm cái tên trong một trường ẩn, còn bước 3 hiển thị cả hai. Hãy đi hết cả ba bước.
Trường ẩn vẫn nhìn thấy được trong mã nguồn trang và sửa được như mọi trường khác, nên chúng phù hợp để mang câu trả lời của chính người truy cập qua các bước, nhưng tuyệt đối không dùng cho giá, user id hay vai trò. Hãy giữ những thứ đó trên máy chủ, trong session; xem session và cookie.
Post/Redirect/Get: tránh gửi lại khi tải lại trang
Sau một POST thành công, nếu script in thẳng trang "cảm ơn", việc nhấn tải lại sẽ khiến trình duyệt gửi lại POST, và đơn hàng hay bình luận bị lưu hai lần. Cách sửa là lưu, chuyển hướng, rồi hiển thị kết quả trên một GET:
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = validate($_POST);
if (!$errors) {
save_comment($_POST);
$_SESSION['flash'] = 'Comment posted.';
header('Location: /comments.php', true, 303);
exit;
}
// On errors, fall through and print the form with the messages.
}
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
303 See Other báo trình duyệt đi theo lệnh chuyển hướng bằng GET. exit sau header() rất quan trọng: thiếu nó, phần còn lại của script vẫn tiếp tục chạy. Việc chuyển hướng cũng phải diễn ra trước mọi đầu ra, nếu không PHP sẽ từ chối với lỗi "headers already sent".
Form thật còn thêm một CSRF token: một giá trị ngẫu nhiên được lưu trong session và trong một trường ẩn, rồi so sánh bằng hash_equals() khi submit, để trang web khác không thể gửi dữ liệu vào form của bạn thay cho một người truy cập đã đăng nhập.
Kiểm tra phần validate mà không cần trình duyệt
Đặt các quy tắc vào một hàm nhận một mảng và trả về các lỗi. Trang gọi hàm với $_POST, còn bạn có thể gọi với bất kỳ mảng nào, nhờ đó dễ dàng thử các trường hợp biên. Hãy thêm trường hợp của riêng bạn vào danh sách.
Trường hợp thứ tư là thứ mọi người hay quên. Một request có thể gửi name[]=x, khiến $_POST['name'] thành một mảng, và truyền mảng vào trim() sẽ ném ra TypeError và dừng script. Kiểm tra is_string() trước sẽ biến đầu vào độc hại thành một lỗi validate bình thường. Phép ép kiểu (string) trong các khối code trước cũng tránh được TypeError, nhưng nó biến mảng thành chữ Array và in ra cảnh báo, nên is_string() là cách kiểm tra gọn gàng hơn.
Lỗi thường gặp: đọc $_POST trước khi form được gửi
Script xử lý form đầu tiên hay gặp nhất thường đọc các trường ngay đầu tệp:
<?php
$name = $_POST['name'];
echo "Hello, $name";
Ở lần truy cập đầu chưa có gì được gửi lên, nên PHP in ra:
Warning: Undefined array key "name" in /var/www/form.php on line 2
Cách sửa là mẫu ở khối code đầu tiên: chỉ xử lý form khi request là POST, và đọc mọi trường kèm giá trị mặc định, $_POST['name'] ?? ''. Nếu cảnh báo xuất hiện sau khi submit, hãy so thuộc tính name trong HTML với khóa trong PHP: name="Name" và $_POST['name'] là hai khóa khác nhau.
Câu hỏi thường gặp
Làm sao gửi form PHP về cùng một trang?
Viết <form method="post"> mà không có action (hoặc action=""). Trình duyệt gửi dữ liệu về URL hiện tại, nên cùng một script vừa hiển thị form vừa xử lý nó: kiểm tra $_SERVER['REQUEST_METHOD'] === 'POST' rồi đọc các trường từ $_POST.
Làm sao giữ lại giá trị form sau khi submit trong PHP?
In lại giá trị đã gửi vào trường, có escape: <input name="email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">. Với checkbox thì in checked, với option của select thì in selected, khi giá trị đã gửi khớp.
Làm sao lấy giá trị của nhiều checkbox trong PHP?
Đặt cho các checkbox cùng một tên kết thúc bằng cặp ngoặc vuông, name="topics[]". Khi đó PHP đưa các giá trị được chọn vào một mảng, $_POST['topics']. Checkbox không được chọn sẽ không gửi gì, nên hãy dùng $_POST['topics'] ?? [] và đối chiếu từng giá trị với danh sách bạn đưa ra.
Vì sao tải lại trang lại gửi lại form PHP?
Request cuối cùng là một POST, nên trình duyệt lặp lại nó khi tải lại trang. Sau khi submit thành công, hãy chuyển hướng bằng header('Location: /thanks.php'); exit; (mẫu Post/Redirect/Get). Khi đó, tải lại trang chỉ lặp lại một GET vô hại.
Dùng $_POST trong PHP có an toàn không?
Không, mọi giá trị trong $_POST đều đến từ người truy cập và có thể là bất cứ thứ gì, kể cả HTML, SQL hay một mảng ở chỗ bạn chờ một chuỗi. Hãy kiểm tra khi nhận vào (filter_var, kiểm tra độ dài và danh sách cho phép) và escape khi xuất ra (htmlspecialchars cho HTML, prepared statement cho SQL).