0

EzCode # Bài 2 — Cú pháp PHP căn bản

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

  • Khai báo và dùng được biến với các kiểu dữ liệu cơ bản
  • Viết được if, switch, for, while và hàm của riêng bạn
  • Định dạng được giá tiền và ngày tháng đúng như EzCode đang làm

Cần học trước: Bài 1


1. Vấn đề đặt ra

Mở trang chủ EzCode, bạn sẽ thấy mỗi khóa học hiện dòng chữ:

Giá: 300,000

Nhưng trong cơ sở dữ liệu, giá của khóa học đó được lưu là con số trần trụi 300000. Không có dấu phẩy nào cả.

Sang trang lịch sử thanh toán, bạn thấy trạng thái đơn hàng ghi "Đang xử lý". Nhưng trong cơ sở dữ liệu, cột status lưu chuỗi pending.

Hai chỗ này cho thấy một quy luật chung của mọi website: dữ liệu lưu trong database khác với thứ hiển thị cho người dùng. Giữa hai bên phải có code làm nhiệm vụ biến đổi.

Bài này dạy bạn những công cụ cơ bản để làm việc biến đổi đó: biến để giữ dữ liệu, điều kiện để chọn cách hiển thị, và hàm để đóng gói lại dùng nhiều lần.


2. Kiến thức mới

2.1. Biến

Biến là một cái hộp có tên, dùng để chứa dữ liệu. Trong PHP, tên biến luôn bắt đầu bằng dấu $.

<?php
$gia = 300000;
$tenKhoaHoc = "Khóa học HTML CSS cơ bản";
$daXuatBan = true;
?>

Bốn điều cần nhớ:

  1. Luôn có dấu $ ở đầu, cả khi khai báo lẫn khi dùng.
  2. Không cần khai báo kiểu. Bạn không viết int $gia như trong Java hay C. PHP tự nhận biết.
  3. Phân biệt chữ hoa chữ thường. $gia$Gia là hai biến khác nhau. Đây là nguồn lỗi rất hay gặp.
  4. Tên biến chỉ chứa chữ cái, chữ số và dấu gạch dưới, và không được bắt đầu bằng chữ số.

Cách đặt tên trong EzCode: tác giả dùng kiểu $tenBien viết liền có chữ hoa ở giữa ($coursesList, $teacherCourses) và cả kiểu $ten_bien có gạch dưới ($course_id, $user_id). Cả hai đều dùng được; bạn nên giữ nguyên đúng tên tác giả đặt khi gõ lại code.

2.2. Các kiểu dữ liệu

Kiểu Tên tiếng Anh Ví dụ Dùng ở đâu trong EzCode
Chuỗi string "Khóa học Python" Tên khóa học, email, mô tả
Số nguyên integer 300000 Giá khóa học, id, thứ tự bài học
Số thực float 300000.50 Số tiền trong bảng payments
Luận lý boolean true, false Kết quả kiểm tra đăng nhập
Rỗng null null Khi truy vấn không tìm thấy dòng nào
Mảng array [1, 2, 3] Danh sách khóa học — học ở Bài 3

Muốn biết một biến đang giữ gì và thuộc kiểu nào, dùng var_dump():

<?php
$gia = 300000;
var_dump($gia);       // in ra: int(300000)

$ten = "Python";
var_dump($ten);       // in ra: string(6) "Python"

$co = true;
var_dump($co);        // in ra: bool(true)
?>

var_dump() là công cụ gỡ lỗi quan trọng nhất của bạn. Khi code chạy sai mà không hiểu vì sao, hãy var_dump() biến ra xem nó thực sự đang chứa gì.

2.3. Chuỗi: nối và nội suy

Có hai cách ghép chuỗi lại với nhau.

Cách 1 — nối bằng dấu chấm:

<?php
$ten = "Hoàng";
echo "Xin chào, " . $ten . "!";     // Xin chào, Hoàng!
?>

Cách 2 — nội suy trong nháy kép:

<?php
$ten = "Hoàng";
echo "Xin chào, $ten!";              // Xin chào, Hoàng!
?>

Nội suy chỉ hoạt động với nháy kép. Nháy đơn in ra nguyên văn:

<?php
$ten = "Hoàng";
echo "Xin chào, $ten!";     // Xin chào, Hoàng!
echo 'Xin chào, $ten!';     // Xin chào, $ten!
?>

Khi biến là phần tử của mảng và nằm trong chuỗi, bạn nên bọc trong ngoặc nhọn cho rõ ràng:

<?php
echo "Giá là {$course['price']} đồng";
?>

Trong EzCode, bạn sẽ gặp nội suy ở những chỗ như header("Location: ?ctrl=course&act=detail&id=$id"); trong Controllers/CourseController.php.

2.4. Toán tử

Toán tử số học: +, -, *, /, % (chia lấy dư).

Toán tử gán rút gọn:

<?php
$sql = "SELECT * FROM courses";
$sql .= " WHERE status = 1";      // nối thêm vào cuối
echo $sql;                         // SELECT * FROM courses WHERE status = 1

$tong = 0;
$tong += 100;                      // tương đương $tong = $tong + 100
?>

Toán tử .= rất quan trọng: đây chính là cách Models/Courses.php dựng câu SQL động ở Bài 12.

Toán tử so sánh:

Toán tử Ý nghĩa Ví dụ
== Bằng nhau về giá trị "5" == 5true
=== Bằng nhau cả giá trị và kiểu "5" === 5false
!= Khác nhau về giá trị "5" != 5false
!== Khác giá trị hoặc khác kiểu "5" !== 5true
> < >= <= Lớn hơn, nhỏ hơn 300000 > 200000true

Vì sao phải phân biệt =====?

PHP rất dễ dãi khi so sánh bằng ==. Nó tự chuyển đổi kiểu trước khi so, dẫn tới kết quả bất ngờ:

<?php
var_dump("5" == 5);        // bool(true)  — chuỗi "5" bị đổi thành số 5
var_dump(0 == "");         // bool(true)  ở PHP 7, bool(false) từ PHP 8
var_dump("abc" == 0);      // bool(false) ở PHP 8
?>

=== không chuyển đổi gì cả, an toàn hơn nhiều.

EzCode dùng cả hai. Ví dụ trong Controllers/UserController.phpif ($_SERVER['REQUEST_METHOD'] === 'POST') dùng ba dấu bằng vì so sánh chuỗi với chuỗi, cần chính xác tuyệt đối. Còn trong Views/payment_history.phpif ($payment['status'] == 'pending') chỉ dùng hai dấu bằng.

Nguyên tắc cho bạn: cứ dùng === mặc định. Chỉ dùng == khi bạn thực sự muốn PHP tự chuyển kiểu.

Toán tử logic: && (và), || (hoặc), ! (phủ định).

<?php
$dangNhap = true;
$laGiangVien = false;

if ($dangNhap && $laGiangVien) {
    echo "Vào được dashboard";
}

if (!$dangNhap) {
    echo "Vui lòng đăng nhập";
}
?>

Đoạn này chính là logic chặn quyền trong Controllers/TeacherController.php mà bạn sẽ viết ở Bài 17.

2.5. Câu lệnh điều kiện if

<?php
$gia = 500000;

if ($gia > 400000) {
    echo "Khóa học cao cấp";
} elseif ($gia > 200000) {
    echo "Khóa học trung cấp";
} else {
    echo "Khóa học phổ thông";
}
?>

2.6. Cú pháp thay thế if: ... endif;

PHP cho phép viết if theo một kiểu khác, thay dấu ngoặc nhọn bằng dấu hai chấm và từ khóa endif;:

<?php
if ($gia > 400000):
    echo "Khóa học cao cấp";
else:
    echo "Khóa học phổ thông";
endif;
?>

Nhìn qua thì có vẻ thừa. Nhưng khi trộn với HTML thì nó dễ đọc hơn hẳn:

<?php if (isset($_SESSION['user_id'])): ?>
    <li><a href="?ctrl=user&act=logout">Đăng xuất</a></li>
<?php else: ?>
    <li><a href="?ctrl=user&act=login">Đăng nhập</a></li>
<?php endif; ?>

So với cách dùng ngoặc nhọn:

<?php if (isset($_SESSION['user_id'])) { ?>
    <li><a href="?ctrl=user&act=logout">Đăng xuất</a></li>
<?php } else { ?>
    <li><a href="?ctrl=user&act=login">Đăng nhập</a></li>
<?php } ?>

Với đoạn ngắn thì khác biệt không nhiều. Nhưng khi khối HTML dài 30 dòng, dấu } lạc lõng giữa HTML rất khó lần ra nó đóng cho cái gì, còn endif; thì nói thẳng ra là nó đóng cho if.

Toàn bộ 22 file trong Views/ của EzCode dùng cú pháp này. Bạn bắt buộc phải quen với nó.

2.7. Câu lệnh switch

Khi phải so sánh một biến với nhiều giá trị cố định, switch gọn hơn chuỗi if elseif:

<?php
$trangThai = "pending";

switch ($trangThai) {
    case 'completed':
        echo 'Thành công';
        break;
    case 'pending':
        echo 'Đang xử lý';
        break;
    default:
        echo 'Không xác định';
}
?>

Ba điểm phải nhớ:

  1. break; là bắt buộc. Thiếu nó, PHP sẽ chạy tiếp xuống các case bên dưới. Đây gọi là "rơi xuyên" (fall-through) và gần như luôn là lỗi.
  2. default: chạy khi không case nào khớp. Luôn nên có, để dữ liệu lạ không làm trang trống trơn.
  3. switch dùng so sánh lỏng ==, không phải ===.

2.8. Vòng lặp forwhile

<?php
// for: khi biết trước số lần lặp
for ($i = 1; $i <= 5; $i++) {
    echo "Bài học số $i <br>";
}

// while: khi lặp tới khi điều kiện sai
$dem = 1;
while ($dem <= 5) {
    echo "Bài học số $dem <br>";
    $dem++;
}
?>

Cấu trúc for gồm ba phần cách nhau bởi dấu chấm phẩy: khởi tạo, điều kiện tiếp tục, và bước nhảy sau mỗi vòng.

Trong thực tế, EzCode gần như không dùng for hay while. Nó dùng foreach để duyệt mảng — Bài 3 sẽ dạy. Nhưng bạn vẫn cần biết hai vòng lặp này vì chúng là kiến thức nền.

2.9. Hàm

Hàm là một đoạn code có tên, đóng gói lại để gọi nhiều lần.

<?php
function tinhGiamGia($gia, $phanTram) {
    $soTienGiam = $gia * $phanTram / 100;
    return $gia - $soTienGiam;
}

echo tinhGiamGia(500000, 20);      // 400000
echo tinhGiamGia(300000, 10);      // 270000
?>
  • function là từ khóa khai báo.
  • $gia$phanTramtham số — dữ liệu truyền vào.
  • return trả kết quả về cho nơi gọi. Gặp return là hàm dừng ngay lập tức.

Tham số mặc định: bạn có thể cho tham số một giá trị sẵn, khi gọi không truyền thì nó dùng giá trị đó.

<?php
function chao($ten, $loiChao = "Xin chào") {
    return "$loiChao, $ten!";
}

echo chao("Hoàng");                    // Xin chào, Hoàng!
echo chao("Hoàng", "Chúc mừng");       // Chúc mừng, Hoàng!
?>

Tham số có giá trị mặc định phải đặt sau cùng. EzCode dùng kỹ thuật này ở TeacherController::students($course_id = null) mà bạn sẽ gặp ở Bài 19: một hàm phục vụ được hai trang khác nhau tùy có truyền tham số hay không.

2.10. number_format() — định dạng số

Đây là hàm giải quyết đúng vấn đề nêu ở mục 1.

<?php
echo number_format(300000);        // 300,000
echo number_format(1234567);       // 1,234,567
?>

Mặc định nó dùng dấu phẩy ngăn cách hàng nghìn theo kiểu Anh Mỹ. Người Việt quen dùng dấu chấm. Muốn vậy thì truyền đủ bốn tham số:

<?php
echo number_format(300000, 0, ',', '.');    // 300.000
?>

Bốn tham số lần lượt là: số cần định dạng, số chữ số thập phân, ký tự ngăn phần thập phân, ký tự ngăn hàng nghìn.

EzCode chỉ dùng dạng một tham số number_format($course['price']), nên trang web hiện 300,000 chứ không phải 300.000. Đây không phải lỗi, chỉ là lựa chọn của tác giả.

2.11. date()strtotime() — định dạng ngày

Cơ sở dữ liệu lưu ngày dưới dạng 2025-08-04 09:05:37. Người Việt muốn đọc 04/08/2025.

<?php
$ngayTuDb = "2025-08-04 09:05:37";
echo date('d/m/Y', strtotime($ngayTuDb));          // 04/08/2025
echo date('d/m/Y H:i', strtotime($ngayTuDb));      // 04/08/2025 09:05
?>
  • strtotime() đổi chuỗi ngày thành một con số mà PHP hiểu được.
  • date() đổi con số đó thành chuỗi theo khuôn dạng bạn chỉ định.

Bảng ký hiệu hay dùng:

Ký hiệu Nghĩa Ví dụ
d Ngày, 2 chữ số 04
m Tháng, 2 chữ số 08
Y Năm, 4 chữ số 2025
H Giờ, 24 giờ 09
i Phút 05

Cặp date('d/m/Y', strtotime(...)) xuất hiện ở Views/user_course.php, Views/payment_history.phpViews/teacher_students.php.


3. Áp dụng vào EzCode

3.1. switch để dịch trạng thái thanh toán

Mở file Views/payment_history.php và tìm tới dòng 52.

📁 Views/payment_history.phpđọc trước

                                    switch($payment['status']) {
                                        case 'completed':
                                            echo 'Thành công';
                                            break;
                                        case 'pending':
                                            echo 'Đang xử lý';
                                            break;
                                        case 'failed':
                                            echo 'Thất bại';
                                            break;
                                        case 'cancelled':
                                            echo 'Đã hủy';
                                            break;
                                        default:
                                            echo 'Không xác định';
                                    }

Đoạn này thụt lề rất sâu vì nó nằm lồng bên trong nhiều lớp thẻ HTML. Khi bạn gõ lại ở Bài 21, hãy giữ nguyên độ thụt lề đó.

Cách nó hoạt động:

  1. $payment['status'] là giá trị lấy từ cột status của bảng payments.
  2. Cột này được khai báo trong ezcode.sqlenum('pending','completed','failed','cancelled'), nghĩa là nó chỉ nhận đúng bốn giá trị đó.
  3. switch so khớp và in ra tiếng Việt tương ứng.
  4. default in "Không xác định". Vì cột là enum nên về lý thuyết default không bao giờ chạy tới, nhưng cột này cho phép NULL, và nếu ai đó thêm giá trị mới vào enum sau này thì default cứu bạn khỏi một ô trống.

Hãy để ý: mỗi case đều có break;. Nếu bỏ break;case 'completed', một đơn hàng thành công sẽ in ra Thành côngĐang xử lý vì PHP rơi xuyên xuống case dưới.

3.2. number_format() để hiển thị giá

Mở Views/page_home.php, dòng 18.

📁 Views/page_home.phpđọc trước

        <p>Giá: <?= number_format($course['price'])?></p>

Một dòng ngắn nhưng gói gọn ba thứ bạn vừa học ở Bài 1 và Bài 2:

  1. Cú pháp rút gọn <?= ?> thay cho <?php echo ?>.
  2. Hàm number_format() biến 300000 thành 300,000.
  3. $course['price'] — lấy phần tử của mảng, Bài 3 sẽ giải thích.

Chú ý là không có dấu cách giữa )?>. Tác giả viết liền. Khi gõ lại ở Bài 10 bạn cứ viết y hệt.


4. Chạy thử

Tạo file hoc/bai02.php:

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

$gia = 300000;
$trangThai = "pending";
$ngayMua = "2025-08-04 09:05:37";

echo "<h2>Thử định dạng dữ liệu</h2>";

echo "<p>Giá gốc trong database: " . $gia . "</p>";
echo "<p>Giá hiển thị kiểu Anh Mỹ: " . number_format($gia) . "</p>";
echo "<p>Giá hiển thị kiểu Việt Nam: " . number_format($gia, 0, ',', '.') . "đ</p>";

echo "<p>Ngày gốc trong database: " . $ngayMua . "</p>";
echo "<p>Ngày hiển thị: " . date('d/m/Y H:i', strtotime($ngayMua)) . "</p>";

echo "<p>Trạng thái gốc: " . $trangThai . "</p>";
echo "<p>Trạng thái hiển thị: ";
switch ($trangThai) {
    case 'completed':
        echo 'Thành công';
        break;
    case 'pending':
        echo 'Đang xử lý';
        break;
    default:
        echo 'Không xác định';
}
echo "</p>";
?>

Mở http://localhost/EZCode_khoahoc/hoc/bai02.php.

Kết quả đúng:

Giá gốc trong database: 300000
Giá hiển thị kiểu Anh Mỹ: 300,000
Giá hiển thị kiểu Việt Nam: 300.000đ
Ngày gốc trong database: 2025-08-04 09:05:37
Ngày hiển thị: 04/08/2025 09:05
Trạng thái gốc: pending
Trạng thái hiển thị: Đang xử lý

Hãy thử đổi $trangThai thành "abc" rồi tải lại — bạn phải thấy "Không xác định", đó là default đang làm việc.


5. Bài tập

Bài tập 1

Viết hàm dinhDangGia($so) trả về chuỗi giá tiền theo kiểu Việt Nam: dấu chấm ngăn hàng nghìn và có chữ đ ở cuối. Ví dụ dinhDangGia(300000) trả về 300.000đ.

Gọi thử hàm với ba giá trị: 300000, 25000, 1500000.

Bài tập 2

Viết hàm tenTrangThai($ma) nhận vào một trong bốn mã pending, completed, failed, cancelledtrả về (không phải echo) chuỗi tiếng Việt tương ứng. Phải có default trả về "Không xác định".

Gọi thử với cả bốn mã và thêm một mã sai như "xyz" để kiểm tra default.

Gợi ý: khác với đoạn trong payment_history.php dùng echo bên trong switch, bài này bạn dùng return. Khi gặp return, hàm dừng ngay, nên bạn không cần break; nữa. Hãy thử cả hai cách để thấy sự khác biệt.

Bài tập 3

Dùng vòng lặp for in ra bảng cửu chương 5 dưới dạng bảng HTML có viền, gồm hai cột: phép tính và kết quả. Ví dụ dòng đầu là 5 x 15.


6. Đáp án

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

📄 hoc/bai02_bt1.php

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

function dinhDangGia($so) {
    return number_format($so, 0, ',', '.') . 'đ';
}

echo "<p>" . dinhDangGia(300000) . "</p>";      // 300.000đ
echo "<p>" . dinhDangGia(25000) . "</p>";       // 25.000đ
echo "<p>" . dinhDangGia(1500000) . "</p>";     // 1.500.000đ
?>

Điểm mấu chốt: hàm này đóng gói một quy tắc hiển thị lại một chỗ. Nếu sau này công ty đổi ý muốn hiện VNĐ thay vì đ, bạn chỉ sửa một dòng trong hàm thay vì đi sửa 15 file View.

Đây chính là điều EzCode chưa làm: tác giả gọi number_format() rải rác ở 12 chỗ khác nhau trong các View, chỗ thì có chữ đ chỗ thì không. Bạn có thể tự thấy sự thiếu nhất quán đó khi so Views/page_home.php (không có đ) với Views/teacher_course.php (có đ).

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

📄 hoc/bai02_bt2.php

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

function tenTrangThai($ma) {
    switch ($ma) {
        case 'completed':
            return 'Thành công';
        case 'pending':
            return 'Đang xử lý';
        case 'failed':
            return 'Thất bại';
        case 'cancelled':
            return 'Đã hủy';
        default:
            return 'Không xác định';
    }
}

echo "<p>completed -> " . tenTrangThai('completed') . "</p>";
echo "<p>pending   -> " . tenTrangThai('pending') . "</p>";
echo "<p>failed    -> " . tenTrangThai('failed') . "</p>";
echo "<p>cancelled -> " . tenTrangThai('cancelled') . "</p>";
echo "<p>xyz       -> " . tenTrangThai('xyz') . "</p>";
?>

Vì sao không cần break;?return làm hàm kết thúc ngay lập tức, không có cơ hội nào để rơi xuyên xuống case dưới. Còn trong payment_history.php, tác giả dùng echoecho chỉ in ra rồi chạy tiếp, nên bắt buộc phải có break; để chặn lại.

Nếu bạn thử xóa hết break; trong phiên bản dùng echo, gọi với pending sẽ in ra Đang xử lýThất bạiĐã hủyKhông xác định.

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

📄 hoc/bai02_bt3.php

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Bảng cửu chương 5</title>
</head>
<body>
    <h2>Bảng cửu chương 5</h2>
    <table border="1" cellpadding="8">
        <tr>
            <th>Phép tính</th>
            <th>Kết quả</th>
        </tr>
        <?php for ($i = 1; $i <= 10; $i++): ?>
            <tr>
                <td>5 x <?= $i ?></td>
                <td><?= 5 * $i ?></td>
            </tr>
        <?php endfor; ?>
    </table>
</body>
</html>

Chú ý ba điểm:

  1. Vòng for dùng cú pháp thay thế for: ... endfor; — cùng một ý tưởng với if: ... endif; ở mục 2.6, và cũng dễ đọc hơn khi trộn HTML.
  2. Trong <td> dùng cú pháp rút gọn <?= ?>.
  3. <?= 5 * $i ?> cho thấy bạn có thể đặt cả một biểu thức tính toán vào trong <?= ?>, không nhất thiết phải là biến.

Cách bố trí này — vòng lặp bao quanh một khối HTML lặp lại — chính là khuôn mẫu của mọi View trong EzCode. Bài 3 sẽ đổi for thành foreach và bạn đã sẵn sàng đọc Views/page_home.php.


➡️ Bài tiếp theo: Bài 3 — Mảng và nhúng PHP vào HTML

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


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí