Bài 16. AJAX với PHP

AJAX cho phép JavaScript gửi yêu cầu tới PHP và cập nhật một phần trang mà không tải lại: tìm kiếm tức thì, kiểm tra tên đăng nhập, lưu dữ liệu nền. Bài này dùng fetch API ở trình duyệt và PHP trả về JSON.

50 phútTrung cấp
Bạn sẽ học được
  • Hiểu luồng AJAX: JS gửi request, PHP trả JSON, JS cập nhật DOM
  • Viết endpoint PHP trả JSON với header đúng
  • Gửi GET, POST (form và JSON) bằng fetch
  • Xử lý lỗi, trạng thái tải, chống XSS khi chèn kết quả
Thuật ngữ cần biết
AJAX
Asynchronous JavaScript and XML: gọi máy chủ nền, nay dùng JSON.
fetch
API JavaScript gửi HTTP request.
Endpoint
Tệp/URL PHP phục vụ dữ liệu, ví dụ api/tim.php.
JSON
Định dạng dữ liệu trao đổi giữa JS và PHP.

Khái niệm và lý thuyết

Trang truyền thống: mỗi thao tác tải lại cả trang. Với AJAX, JavaScript gọi một tệp PHP “ngầm”, PHP chỉ trả dữ liệu (JSON) thay vì HTML đầy đủ, JavaScript nhận và vẽ lại phần cần thiết. PHP phía máy chủ vẫn phải xác thực và làm sạch dữ liệu như form thường.

Endpoint PHP trả JSON

<?php
// api/tim.php
declare(strict_types=1);
header("Content-Type: application/json; charset=utf-8");
$q = trim($_GET["q"] ?? "");
$ds = [
    ["ma" => "SV001", "ten" => "Lò Văn An", "lop" => "K66 Tin"],
    ["ma" => "SV002", "ten" => "Cầm Thị Bình", "lop" => "K66 Tin"],
    ["ma" => "SV003", "ten" => "Vì Văn Chi", "lop" => "K65 Tin"],
];  // thực tế: truy vấn CSDL bằng prepared statement
$kq = array_values(array_filter($ds, fn($s) => $q === "" || mb_stripos($s["ten"], $q) !== false));
echo json_encode(["ok" => true, "so_luong" => count($kq), "du_lieu" => $kq], JSON_UNESCAPED_UNICODE);

Mở trực tiếp api/tim.php?q=an trên trình duyệt để kiểm tra JSON trước khi viết JavaScript.

Tìm kiếm tức thì (GET)

<input id="o-tim" placeholder="Gõ tên sinh viên..." autocomplete="off">
<p id="trang-thai"></p>
<ul id="kq"></ul>
<script>
const oTim = document.getElementById("o-tim"), ul = document.getElementById("kq"), tt = document.getElementById("trang-thai");
let hen;
oTim.addEventListener("input", () => {
  clearTimeout(hen);
  hen = setTimeout(timKiem, 300);          // debounce: chờ ngừng gõ 300ms
});
async function timKiem() {
  tt.textContent = "Đang tìm...";
  try {
    const res = await fetch("api/tim.php?q=" + encodeURIComponent(oTim.value));
    if (!res.ok) throw new Error("HTTP " + res.status);
    const json = await res.json();
    ul.innerHTML = "";
    for (const sv of json.du_lieu) {
      const li = document.createElement("li");
      li.textContent = sv.ma + " - " + sv.ten + " (" + sv.lop + ")";   // textContent: an toàn
      ul.append(li);
    }
    tt.textContent = json.so_luong + " kết quả";
  } catch (e) {
    tt.textContent = "Lỗi: " + e.message;
  }
}
</script>

Gửi dữ liệu (POST)

<form id="f-them">
  <input name="ten" required> <input name="lop" required> <button>Thêm</button>
</form>
<script>
document.getElementById("f-them").addEventListener("submit", async e => {
  e.preventDefault();
  // Cách 1: gửi như form thường -> PHP đọc $_POST
  const res = await fetch("api/them.php", { method: "POST", body: new FormData(e.target) });
  // Cách 2: gửi JSON -> PHP đọc php://input
  // const res = await fetch("api/them.php", { method: "POST",
  //   headers: { "Content-Type": "application/json" },
  //   body: JSON.stringify(Object.fromEntries(new FormData(e.target))) });
  const json = await res.json();
  alert(json.ok ? "Đã thêm " + json.ten : "Lỗi: " + json.loi);
});
</script>
<?php
// api/them.php
header("Content-Type: application/json; charset=utf-8");
if ($_SERVER["REQUEST_METHOD"] !== "POST") { http_response_code(405); exit(json_encode(["ok" => false, "loi" => "Chỉ nhận POST"])); }
$ct = $_SERVER["CONTENT_TYPE"] ?? "";
$in = str_starts_with($ct, "application/json") ? (json_decode(file_get_contents("php://input"), true) ?? []) : $_POST;
$ten = trim($in["ten"] ?? "");
if (mb_strlen($ten) < 3) { http_response_code(422); exit(json_encode(["ok" => false, "loi" => "Tên quá ngắn"], JSON_UNESCAPED_UNICODE)); }
// lưu CSDL...
echo json_encode(["ok" => true, "ten" => $ten], JSON_UNESCAPED_UNICODE);
Mẹo hay

Trả mã trạng thái đúng (http_response_code(422) cho dữ liệu sai, 404, 500) để JavaScript phân biệt lỗi qua res.ok. Xem loạt bài JavaScript, Bài 7 để hiểu sâu Promise/async.

Lưu ý

Endpoint AJAX vẫn cần kiểm tra đăng nhập (session) và CSRF token (gửi trong header hoặc body) như trang thường; kẻ xấu gọi được thẳng api/them.php.

Thực hành
  1. Làm ô kiểm tra “tên đăng nhập đã tồn tại?” báo ngay khi người dùng gõ xong.
  2. Hoàn thiện tìm kiếm tức thì có hiển thị trạng thái tải và lỗi.
  3. Làm nút “Thích” tăng số lượt bằng POST mà không tải lại trang.
  4. Chuyển endpoint tìm kiếm sang đọc từ CSDL (sau loạt PHP và MySQL).
Tóm tắt bài học
  • AJAX: JS fetch → PHP trả JSON → JS cập nhật DOM.
  • Endpoint đặt header JSON, json_encode(JSON_UNESCAPED_UNICODE).
  • POST qua FormData ($_POST) hoặc JSON (php://input).
  • Debounce, textContent, mã trạng thái, kiểm tra quyền và CSRF.