0

EzCode # Bài 4 — Form, `$_GET`, `$_POST` và `header()`

Học xong bài này bạn sẽ:

  • Nhận được dữ liệu người dùng gõ vào form và kiểm tra tính hợp lệ của nó
  • Hiểu địa chỉ ?ctrl=course&act=detail&id=1 của EzCode được đọc ra sao
  • Chuyển hướng trang bằng header() và biết vì sao luôn phải kèm exit()

Cần học trước: Bài 2, Bài 3


1. Vấn đề đặt ra

Người dùng mở trang đăng nhập của EzCode, gõ email và mật khẩu, rồi bấm nút.

Hai câu hỏi:

  1. Dữ liệu đó đi đâu? Nó rời khỏi trình duyệt, đi qua mạng, tới máy chủ. Nhưng dưới hình thức nào?
  2. PHP nhặt lại bằng cách nào? Trong file Controllers/UserController.php không có dòng nào nói "hãy đọc ô email". Vậy $email từ đâu ra?

Câu trả lời nằm ở hai mảng có sẵn của PHP: $_GET$_POST. Bài này dạy bạn dùng chúng, kiểm tra dữ liệu nhận được, và chuyển hướng người dùng sang trang khác sau khi xử lý xong.


2. Kiến thức mới

2.1. Thẻ <form> và thuộc tính name

<form method="POST">
    <input type="text" name="email">
    <input type="password" name="password">
    <button type="submit">Đăng nhập</button>
</form>

Thuộc tính quan trọng nhất là name. Nó quyết định tên khóa trong mảng mà PHP nhận được.

Ô có name="email" sẽ trở thành $_POST['email']. Ô không có name thì dữ liệu bị vứt bỏ, PHP không nhận được gì cả. Đây là lỗi hay gặp: form nhìn đẹp, bấm nút không báo lỗi, nhưng dữ liệu mất sạch chỉ vì quên name.

Thuộc tính id chỉ dùng cho CSS và JavaScript, không ảnh hưởng tới PHP. Trong EzCode bạn sẽ thấy tác giả đặt cả hai: <input type="email" id="email" name="email">.

2.2. $_GET$_POST

Đây là hai mảng kết hợp có sẵn của PHP. Bạn không phải khai báo, PHP tự tạo cho mỗi lần trang được gọi.

$_GET $_POST
Dữ liệu đến từ Chuỗi truy vấn trên URL Thân của yêu cầu, không hiện trên URL
Nhìn thấy trên thanh địa chỉ Không
Bookmark lại được Không
Bấm F5 có gửi lại Không hỏi lại Trình duyệt hỏi "gửi lại biểu mẫu?"
Giới hạn độ dài Có, khoảng 2000 ký tự Rất lớn
Gửi được file Không
Dùng cho Xem, tìm kiếm, lọc Tạo, sửa, xóa, đăng nhập

Nguyên tắc chọn: nếu thao tác chỉ đọc dữ liệu thì dùng GET; nếu nó thay đổi dữ liệu thì dùng POST.

EzCode theo đúng nguyên tắc này: trang lọc khóa học dùng <form method="GET"> để người dùng chia sẻ được đường dẫn kết quả lọc, còn form đăng ký và đăng nhập dùng method="POST" để mật khẩu không hiện lên thanh địa chỉ.

2.3. Chuỗi truy vấn trên URL

Nhìn địa chỉ này:

?ctrl=course&act=detail&id=1

Phần sau dấu ? gọi là chuỗi truy vấn (query string). Nó gồm các cặp khóa=giá trị nối với nhau bằng dấu &.

PHP tự tách nó thành mảng $_GET:

$_GET = [
    'ctrl' => 'course',
    'act'  => 'detail',
    'id'   => '1'
];

Nên trong code bạn đọc được:

<?php
echo $_GET['ctrl'];      // course
echo $_GET['act'];       // detail
echo $_GET['id'];        // 1
?>

Đây chính là cơ chế định tuyến của EzCode. Website này không có file danhsachkhoahoc.php hay chitiet.php riêng biệt. Mọi trang đều là index.php, và ba tham số ctrl, act, id quyết định trang nào được hiển thị. Bài 9 sẽ dựng cơ chế đó.

Một điều quan trọng: mọi giá trị trong $_GET$_POST đều là chuỗi, kể cả khi trông như số. $_GET['id'] là chuỗi "1" chứ không phải số 1. Đây là lý do bạn nên dùng == thay vì === khi so sánh chúng với số, hoặc chủ động ép kiểu bằng (int).

2.4. Ô nhập dạng mảng: name="teacher[]"

Khi form có nhiều ô cùng tên, bạn thêm cặp ngoặc vuông vào cuối:

<input type="checkbox" name="teacher[]" value="1"> Nguyễn Văn A
<input type="checkbox" name="teacher[]" value="5"> Ngô Văn E
<input type="checkbox" name="teacher[]" value="9"> Nguyễn Văn I

Nếu người dùng tick ô thứ nhất và thứ ba, PHP nhận được:

$_GET['teacher'] = ['1', '9'];

Một mảng, không phải một chuỗi. Bạn duyệt nó bằng foreach như đã học ở Bài 3.

URL sinh ra sẽ có dạng ?teacher[]=1&teacher[]=9.

Kỹ thuật này được dùng ở Views/course_list.php cho bộ lọc giảng viên và danh mục. Bài 12 sẽ làm chi tiết.

Cảnh báo: nếu người dùng không tick ô nào, $_GET['teacher'] không tồn tại chứ không phải là mảng rỗng. Vì vậy phải luôn kiểm tra bằng isset() trước khi dùng.

2.5. $_SERVER['REQUEST_METHOD']

Một trang có form được mở trong hai tình huống khác nhau:

  1. Lần đầu, người dùng bấm liên kết vào trang. Đây là yêu cầu GET. Ta chỉ cần hiện form trống.
  2. Sau khi bấm nút gửi. Đây là yêu cầu POST. Ta phải xử lý dữ liệu.

Cùng một file PHP phải phân biệt được hai tình huống. Công cụ là:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Người dùng vừa bấm nút gửi — xử lý dữ liệu ở đây
}

// Dù trường hợp nào cũng hiện form ở dưới
?>

$_SERVER là mảng chứa thông tin về yêu cầu hiện tại. Khóa REQUEST_METHOD giữ chuỗi "GET" hoặc "POST" viết hoa.

Mẫu này xuất hiện ở 6 chỗ trong EzCode: UserController::register, login, updateProfile, changePassword, và TeacherController::createCourse, editCourse, createLesson. Nhận ra nó là bạn đọc được nửa số Controller.

Vài khóa $_SERVER khác mà EzCode dùng:

Khóa Nội dung Dùng ở đâu
REQUEST_METHOD GET hoặc POST Mọi Controller có form
HTTP_HOST localhost PaymentController::create
REMOTE_ADDR Địa chỉ IP người dùng Models/VNPay.php

2.6. Toán tử ?? — giá trị thay thế

Nếu người dùng bỏ trống một ô, hoặc kẻ xấu gửi form thiếu trường, thì $_POST['name'] không tồn tại. Truy cập nó sẽ sinh cảnh báo:

Warning: Undefined array key "name"

Cách xử lý cũ dài dòng:

<?php
if (isset($_POST['name'])) {
    $name = $_POST['name'];
} else {
    $name = '';
}
?>

PHP 7 trở đi có toán tử ?? viết gọn lại thành một dòng:

<?php
$name = $_POST['name'] ?? '';
?>

Đọc là: "lấy $_POST['name'], nếu nó không tồn tại hoặc bằng null thì dùng chuỗi rỗng".

Toán tử này xuất hiện dày đặc trong EzCode. Ngoài việc đọc form, nó còn dùng khi lấy dữ liệu từ database có thể trả về null:

return $result['total'] ?? 0;

Dòng trên nằm trong Models/Courses.php, Bài 20 sẽ gặp lại.

2.7. Kiểm tra dữ liệu và mẫu gom lỗi

Không bao giờ tin dữ liệu người dùng gửi lên. Thuộc tính required của HTML chỉ chặn ở trình duyệt; ai cũng có thể tắt JavaScript hoặc gửi yêu cầu thẳng bằng công cụ khác.

Mọi kiểm tra bắt buộc phải làm lại ở máy chủ.

EzCode dùng một mẫu rất dễ theo: gom mọi lỗi vào một mảng, kiểm tra mảng đó rỗng hay không.

<?php
$errors = [];

if (empty($name))  $errors[] = "Tên không được để trống";
if (empty($email)) $errors[] = "Email không được để trống";

if (empty($errors)) {
    // Không có lỗi nào — tiến hành xử lý
} else {
    // Có lỗi — hiện lại form kèm danh sách lỗi
}
?>

Ưu điểm của mẫu này: người dùng thấy tất cả lỗi cùng lúc, thay vì sửa một lỗi lại bị báo lỗi tiếp theo.

Kiểm tra email bằng filter_var():

<?php
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = "Email không hợp lệ";
}
?>

filter_var() với cờ FILTER_VALIDATE_EMAIL trả về email nếu hợp lệ, hoặc false nếu không. Dấu ! phía trước đảo ngược kết quả, nên câu điều kiện đọc là "nếu email không hợp lệ thì thêm lỗi".

Đừng tự viết biểu thức chính quy kiểm tra email. Quy tắc email đúng chuẩn phức tạp hơn bạn tưởng rất nhiều, và filter_var đã làm sẵn.

2.8. header() — chuyển hướng trang

Sau khi xử lý xong, thường ta muốn đưa người dùng sang trang khác:

<?php
header("Location: ?ctrl=user&act=login");
exit();
?>

header() gửi một dòng lệnh đặc biệt về cho trình duyệt, bảo nó chuyển sang địa chỉ khác.

exit() là bắt buộc. Đây là điểm người mới hay bỏ sót, và hậu quả nghiêm trọng.

Lý do: header() không dừng chương trình. Nó chỉ đặt một dòng lệnh vào phần đầu của phản hồi rồi PHP chạy tiếp các dòng dưới. Nếu bên dưới còn code truy vấn database hay in dữ liệu nhạy cảm, chúng vẫn chạy và vẫn gửi về, mặc dù trình duyệt sẽ chuyển trang.

Ví dụ nguy hiểm:

<?php
if (!isset($_SESSION['user_id'])) {
    header("Location: ?ctrl=user&act=login");
    // THIẾU exit() ở đây
}

// Dòng này vẫn chạy dù người dùng chưa đăng nhập!
echo "Thông tin thẻ tín dụng: " . $thongTinNhayCam;
?>

Trình duyệt bình thường sẽ chuyển trang nên bạn không thấy vấn đề. Nhưng ai đó dùng công cụ xem phản hồi thô sẽ đọc được toàn bộ nội dung phía dưới.

Quy tắc: cứ viết header() là viết exit(); ngay dòng dưới. EzCode làm đúng điều này ở cả 30 chỗ có chuyển hướng.

2.9. Lỗi "headers already sent"

Đây là lỗi bạn chắc chắn sẽ gặp:

Warning: Cannot modify header information - headers already sent by (output started at ...)

Nguyên nhân: một phản hồi HTTP gồm hai phần, phần đầu (headers) rồi mới tới phần thân (nội dung HTML). Khi PHP đã in ra dù chỉ một ký tự, phần đầu coi như đã gửi đi và không sửa được nữa. Gọi header() lúc đó là quá muộn.

Những thứ tính là "đã in ra":

  • Bất kỳ lệnh echo hay print nào
  • Bất kỳ ký tự HTML nào nằm ngoài thẻ <?php ?>
  • Một dòng trống hoặc một dấu cách trước thẻ <?php — thủ phạm khó tìm nhất

Cách phòng: trong các file chỉ chứa PHP như Model và Controller, đừng viết ?> ở cuối file. Thiếu thẻ đóng thì không thể có ký tự thừa lọt ra sau nó.

Tuy nhiên EzCode vẫn gọi header() sau khi đã in HTML ra, ở khắp nơi. Nó chạy được là nhờ một tính năng của PHP tên là output_buffering — PHP giữ nội dung trong bộ nhớ đệm thay vì gửi ngay. Đây là lỗi số 13 trong Bài 22, và mục 3.2 dưới đây sẽ chỉ ra chính xác chỗ gây ra nó.


3. Áp dụng vào EzCode

3.1. Nhận và kiểm tra dữ liệu form đăng ký

Mở Controllers/UserController.php, phần đầu của phương thức register().

📁 Controllers/UserController.phpđọc trước

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = $_POST['name'] ?? '';
            $email = $_POST['email'] ?? '';
            $password = $_POST['password'] ?? '';
            $confirm_password = $_POST['confirm_password'] ?? '';
            $phone = $_POST['phone'] ?? '';
            $role = 'student'; // Mặc định role student

            // Validation
            $errors = [];

            if (empty($name)) $errors[] = "Tên không được để trống";
            if (empty($email)) $errors[] = "Email không được để trống";
            if (empty($password)) $errors[] = "Mật khẩu không được để trống";
            if ($password !== $confirm_password) $errors[] = "Mật khẩu xác nhận không khớp";
            if (empty($phone)) $errors[] = "Số điện thoại không được để trống";
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "Email không hợp lệ";

Đoạn này dùng đúng bốn thứ bạn vừa học, theo thứ tự:

  1. Dòng 1$_SERVER['REQUEST_METHOD'] === 'POST' phân biệt "mở trang" với "bấm nút gửi".
  2. Dòng 2 đến 6 — lấy năm ô của form bằng $_POST['...'] ?? ''. Tên khóa name, email, password, confirm_password, phone khớp chính xác với thuộc tính name của năm thẻ <input> trong Views/user_register.php.
  3. Dòng 7$role = 'student'; được gán cứng, không lấy từ form. Đây là một quyết định bảo mật quan trọng: nếu lấy $_POST['role'] thì ai cũng có thể tự đăng ký làm giảng viên bằng cách thêm một trường ẩn vào form. Tác giả làm đúng ở đây.
  4. Dòng 10 đến 17 — mẫu gom lỗi vào $errors.

Hãy để ý dòng kiểm tra mật khẩu xác nhận dùng !== ba dấu bằng. Nếu dùng != hai dấu, PHP sẽ tự chuyển kiểu và có thể cho qua những cặp mật khẩu thực ra khác nhau.

Cũng để ý cách viết if (điều kiện) câu_lệnh; trên một dòng, không có ngoặc nhọn. PHP cho phép khi thân if chỉ có đúng một câu lệnh. Nó gọn nhưng dễ gây lỗi nếu sau này bạn thêm câu lệnh thứ hai mà quên thêm ngoặc.

3.2. Cửa vào của website

Bây giờ mở index.php và nhìn bốn dòng ở giữa file.

📁 index.phpđọc trước

session_start();
include_once "./Views/layout_header.php";
//?ctrl=product&act=detail&id=1
if (isset($_GET['ctrl']) && isset($_GET['act'])) {

Bốn dòng, ba điều đáng nói.

Dòng session_start(); — khởi động phiên làm việc. Bài 5 sẽ dạy.

Dòng ghi chú //?ctrl=product&act=detail&id=1 — tác giả để lại ví dụ về dạng địa chỉ mà file này xử lý. Chữ product là dấu vết của một project cũ; trong EzCode thì ctrl sẽ là page, course, user, teacher, payment hoặc learn.

Dòng if (isset($_GET['ctrl']) && isset($_GET['act'])) — kiểm tra URL có đủ hai tham số điều hướng không. Dùng isset() chứ không phải empty(), vì ta chỉ cần biết chúng có mặt hay không. Toán tử && bắt buộc cả hai cùng có.

Nếu thiếu một trong hai, code sẽ nhảy xuống nhánh else và mở trang chủ. Đó là lý do gõ http://localhost/EZCode_khoahoc/ trần trụi vẫn ra được trang chủ.

⚠️ Lưu ý: Dòng include_once "./Views/layout_header.php"; nằm trước khi bất kỳ Controller nào chạy. Nghĩa là toàn bộ phần đầu trang HTML đã được in ra trước khi code xử lý bắt đầu.

Nhưng gần như mọi Controller đều gọi header("Location: ...") để chuyển hướng. Theo mục 2.9, lúc đó headers đã gửi đi rồi, lẽ ra phải lỗi.

Nó không lỗi chỉ vì XAMPP bật sẵn output_buffering. Tắt tùy chọn đó trong php.ini là toàn bộ chức năng đăng nhập, đăng ký, thanh toán của EzCode ngừng hoạt động. Đây là lỗi số 13 trong Bài 22, và là một lỗi kiến trúc chứ không phải lỗi nhỏ.


4. Chạy thử

Tạo file hoc/bai04.php:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$ketQua = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $ten   = $_POST['ten'] ?? '';
    $email = $_POST['email'] ?? '';

    $errors = [];
    if (empty($ten))   $errors[] = "Tên không được để trống";
    if (empty($email)) $errors[] = "Email không được để trống";
    if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "Email không hợp lệ";
    }

    if (empty($errors)) {
        $ketQua = "Chào $ten, email của bạn là $email";
    }
}
?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Bài 4 - Form</title>
</head>
<body>
    <h2>Thử nghiệm form POST</h2>

    <?php if (!empty($errors)): ?>
        <div style="color: #721c24; background: #f8d7da; padding: 10px;">
            <?php foreach ($errors as $error): ?>
                <div><?= $error ?></div>
            <?php endforeach; ?>
        </div>
    <?php endif; ?>

    <?php if ($ketQua): ?>
        <div style="color: #155724; background: #d4edda; padding: 10px;">
            <?= $ketQua ?>
        </div>
    <?php endif; ?>

    <form method="POST">
        <p>
            <label>Tên:</label>
            <input type="text" name="ten" value="<?= $_POST['ten'] ?? '' ?>">
        </p>
        <p>
            <label>Email:</label>
            <input type="text" name="email" value="<?= $_POST['email'] ?? '' ?>">
        </p>
        <button type="submit">Gửi</button>
    </form>

    <hr>
    <h3>Thử nghiệm GET</h3>
    <p>Hãy thêm <code>?mon=PHP&buoi=4</code> vào cuối địa chỉ rồi tải lại.</p>
    <p>Môn học: <?= $_GET['mon'] ?? '(chưa có)' ?></p>
    <p>Buổi: <?= $_GET['buoi'] ?? '(chưa có)' ?></p>

    <hr>
    <h3>Nội dung hai mảng</h3>
    <p>Phương thức hiện tại: <strong><?= $_SERVER['REQUEST_METHOD'] ?></strong></p>
    <pre>$_GET = <?php print_r($_GET); ?></pre>
    <pre>$_POST = <?php print_r($_POST); ?></pre>
</body>
</html>

Các bước thử:

  1. Mở http://localhost/EZCode_khoahoc/hoc/bai04.php. Phương thức hiển thị là GET, cả hai mảng đều rỗng.
  2. Bấm Gửi khi form trống. Bạn thấy hai lỗi màu đỏ, phương thức đổi thành POST, và $_POST có hai khóa với giá trị rỗng.
  3. Gõ tên và email sai định dạng như abc, bấm Gửi. Bạn thấy lỗi "Email không hợp lệ".
  4. Gõ email đúng như test@gmail.com, bấm Gửi. Bạn thấy dòng chào màu xanh, và giá trị bạn gõ vẫn còn trong ô — nhờ value="<?= $_POST['ten'] ?? '' ?>".
  5. Thêm ?mon=PHP&buoi=4 vào cuối địa chỉ. Hai dòng dưới hiện PHP4, còn $_GET có hai khóa.
  6. Nhìn thanh địa chỉ: dữ liệu GET hiện ra, dữ liệu POST thì không.

Bước 4 là thứ đáng chú ý nhất: giữ lại giá trị người dùng đã gõ khi form báo lỗi. Không có nó, người dùng phải gõ lại từ đầu mỗi lần sai. EzCode làm đúng điều này ở Views/user_register.php.


5. Bài tập

Bài tập 1

Tạo hoc/bai04_bt1.php là form đăng ký gồm ba ô: họ tên, email, số điện thoại.

Khi bấm gửi, kiểm tra ở phía máy chủ:

  • Cả ba ô đều không được để trống
  • Email phải hợp lệ theo filter_var

Gom lỗi vào mảng $errors và in ra dưới dạng danh sách màu đỏ phía trên form. Giữ lại giá trị đã gõ trong các ô.

Bài tập 2

Thêm vào form trên hai ô mật khẩu: passwordconfirm_password, cả hai dùng type="password".

Kiểm tra thêm:

  • Mật khẩu không được để trống
  • Hai ô mật khẩu phải khớp nhau (dùng !==)
  • Mật khẩu phải dài ít nhất 6 ký tự (dùng strlen())

Chú ý: không được điền lại giá trị vào ô mật khẩu bằng value=. Hãy tự suy nghĩ vì sao, rồi đối chiếu với đáp án.

Bài tập 3

Khi không còn lỗi nào, thay vì in dòng chào, hãy chuyển hướng sang một file mới hoc/bai04_ok.php bằng header() kèm exit().

Tạo luôn file hoc/bai04_ok.php chỉ in ra dòng "Đăng ký thành công!" và một liên kết quay lại form.

Sau khi làm xong, hãy thử xóa dòng exit(); và thêm một dòng echo "Dòng này lẽ ra không được chạy"; ngay bên dưới header(). Mô tả điều bạn quan sát được và giải thích.


6. Đáp án

Đáp án bài tập 1 và 2

📄 hoc/bai04_bt1.php

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name             = $_POST['name'] ?? '';
    $email            = $_POST['email'] ?? '';
    $phone            = $_POST['phone'] ?? '';
    $password         = $_POST['password'] ?? '';
    $confirm_password = $_POST['confirm_password'] ?? '';

    if (empty($name))     $errors[] = "Tên không được để trống";
    if (empty($email))    $errors[] = "Email không được để trống";
    if (empty($phone))    $errors[] = "Số điện thoại không được để trống";
    if (empty($password)) $errors[] = "Mật khẩu không được để trống";

    if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "Email không hợp lệ";
    }
    if ($password !== $confirm_password) {
        $errors[] = "Mật khẩu xác nhận không khớp";
    }
    if (!empty($password) && strlen($password) < 6) {
        $errors[] = "Mật khẩu phải có ít nhất 6 ký tự";
    }

    if (empty($errors)) {
        header("Location: bai04_ok.php");
        exit();
    }
}
?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Đăng ký</title>
</head>
<body>
    <h2>Đăng ký tài khoản</h2>

    <?php if (!empty($errors)): ?>
        <div style="color: #721c24; background: #f8d7da; padding: 10px; margin-bottom: 15px;">
            <?php foreach ($errors as $error): ?>
                <div><?= $error ?></div>
            <?php endforeach; ?>
        </div>
    <?php endif; ?>

    <form method="POST">
        <p>
            <label>Họ và tên *</label><br>
            <input type="text" name="name" value="<?= $_POST['name'] ?? '' ?>">
        </p>
        <p>
            <label>Email *</label><br>
            <input type="text" name="email" value="<?= $_POST['email'] ?? '' ?>">
        </p>
        <p>
            <label>Số điện thoại *</label><br>
            <input type="text" name="phone" value="<?= $_POST['phone'] ?? '' ?>">
        </p>
        <p>
            <label>Mật khẩu *</label><br>
            <input type="password" name="password">
        </p>
        <p>
            <label>Xác nhận mật khẩu *</label><br>
            <input type="password" name="confirm_password">
        </p>
        <button type="submit">Đăng ký</button>
    </form>
</body>
</html>

Vì sao không điền lại giá trị vào ô mật khẩu?

Ba lý do:

  1. Bảo mật. Mật khẩu sẽ nằm trong mã nguồn HTML của trang. Bất kỳ ai nhìn màn hình, hoặc bất kỳ tiện ích trình duyệt nào, đều đọc được.
  2. Lịch sử trình duyệt và bộ nhớ đệm. Trang có mật khẩu trong HTML có thể bị lưu lại.
  3. Thói quen tốt. Người dùng gõ lại mật khẩu không mất công bằng gõ lại họ tên và email.

Hãy mở Views/user_register.php của EzCode và kiểm chứng: ba ô name, email, phone đều có value=, còn hai ô mật khẩu thì không. Tác giả làm đúng.

Vì sao kiểm tra email lại có thêm !empty($email) && phía trước?

Nếu ô email trống, ta đã báo lỗi "Email không được để trống" rồi. Không cần báo thêm "Email không hợp lệ" nữa — hai lỗi cho cùng một ô làm người dùng rối. Điều kiện !empty($email) chặn lỗi thứ hai.

Đây là chỗ code EzCode làm chưa tốt: trong UserController::register(), để trống ô email sẽ hiện cả hai thông báo cùng lúc. Không phải lỗi nghiêm trọng, nhưng là điểm bạn có thể cải thiện.

Đáp án bài tập 3

📄 hoc/bai04_ok.php

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Thành công</title>
</head>
<body>
    <h2 style="color: green;">Đăng ký thành công!</h2>
    <p><a href="bai04_bt1.php">Quay lại form</a></p>
</body>
</html>

Phần quan sát khi xóa exit();:

Sửa đoạn cuối thành:

    if (empty($errors)) {
        header("Location: bai04_ok.php");
        echo "Dòng này lẽ ra không được chạy";
    }

Điền form hợp lệ rồi bấm Gửi. Bạn vẫn thấy trang "Đăng ký thành công!" như thường. Nhìn bề ngoài không có gì khác.

Nhưng dòng echo kia đã chạy và nội dung của nó đã được gửi về trình duyệt. Chỉ là trình duyệt vứt bỏ nó đi vì đang bận chuyển trang.

Cách kiểm chứng: mở Developer Tools bằng phím F12, sang tab Network, tick ô Preserve log, rồi gửi form lại. Bấm vào yêu cầu bai04_bt1.php có mã trạng thái 302, mở tab Response. Bạn sẽ đọc thấy dòng chữ đó nằm trong nội dung phản hồi.

Vì sao điều này nguy hiểm? Hãy tưởng tượng đoạn code sau header() không phải là một dòng chữ vô hại, mà là:

if (!isset($_SESSION['user_id'])) {
    header("Location: ?ctrl=user&act=login");
    // quên exit()
}
$donHang = $model->layTatCaDonHang();   // vẫn chạy
include "./Views/danh_sach_don_hang.php"; // vẫn in ra

Người chưa đăng nhập vẫn nhận được toàn bộ danh sách đơn hàng trong phản hồi, chỉ cần một công cụ đơn giản là đọc được. Việc kiểm tra quyền coi như vô nghĩa.

Kết luận: header() là lệnh gợi ý cho trình duyệt, không phải lệnh dừng chương trình. Muốn dừng thật thì phải exit().


➡️ Bài tiếp theo: Bài 5 — Session và thông báo flash

⬅️ Về mục lục · Bài trước


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.