
PHP EP 20: Transaction PDO, khóa ngoại và Repository trong PHP
Quản lý giao dịch cơ sở dữ liệu bằng beginTransaction, commit và rollBack trong PHP PDO, thực hành bật ràng buộc khóa ngoại và đóng gói truy vấn vào Repository.

Nắm vững biến, hằng số có kiểu và 4 kiểu vô hướng trong PHP, thực hành kiểm tra giá trị bằng var_dump và phân biệt ép kiểu tự động với số học chính xác.
Khi xử lý dữ liệu cấu hình hoặc tính toán hóa đơn trong PHP, người mới học thường vấp phải hai hiện tượng gây hoang mang: thứ nhất, khi in một biến mang giá trị false hoặc null bằng lệnh echo, màn hình hoàn toàn trống trơn khiến người viết tưởng biến chưa tồn tại; thứ hai, phép so sánh (0.1 + 0.2) === 0.3 lại trả về false dù phép cộng thập phân trên giấy rõ ràng bằng 0.3. Ngoài ra, khi đọc dữ liệu từ tham số dòng lệnh hoặc biểu mẫu HTTP, mọi giá trị ban đầu đều là chuỗi ký tự (string), nếu đem cộng trực tiếp với số nguyên sẽ kích hoạt cơ chế tự động chuyển kiểu (Type Juggling) kèm các cảnh báo tiềm ẩn.
Bài học thứ tư trong lộ trình PHP nối tiếp bài học về strict_types ở PHP EP 3. Dựa trên tài liệu chính thức về Hệ thống Kiểu dữ liệu (Types), Hằng số lớp có kiểu (Typed Class Constants) và đối tượng số học chính xác BcMath\Number trên PHP 8.4, chúng ta sẽ phân tích 4 kiểu dữ liệu vô hướng (Scalar), cách dùng var_dump() để soi đúng bản chất của biến, ranh giới ép kiểu tự động và cách tính toán thập phân chính xác tuyệt đối.
Hồ sơ kiểm chứng & Môi trường Lab: Ngày kiểm chứng: Các kịch bản thực hành trong bài được kiểm chứng ngày 26/10/2026 bên trong container Docker cô lập mạng (--network none), tệp gốc chỉ đọc (--read-only) kèm vùng tạm /tmp, chạy dưới tài khoản thường phplab (UID 1001) trên Alpine Linux (aarch64) với PHP 8.4.27 (cli), Zend Engine v4.4.27 và tiện ích mở rộng bcmath đã kích hoạt. Bài viết chỉ khảo sát các kiểu vô hướng, null và BcMath\Number; kiểu mảng và đối tượng sẽ được học sâu ở Chặng 2 và Chặng 3. Giới hạn kiểm chứng: CLI cục bộ, chưa kiểm chứng production.
Trong PHP, mỗi biến bắt đầu bằng ký hiệu đô-la ($tenBien) và giá trị của nó được lưu trữ bên trong cấu trúc nội bộ của Zend Engine gọi là zval (chứa cả giá trị lẫn nhãn kiểu dữ liệu). Nhóm kiểu dữ liệu cơ bản nhất giữ một giá trị đơn lẻ được gọi là kiểu vô hướng (Scalar types) gồm bool, int, float và string, bên cạnh kiểu đặc biệt null.
| Kiểu / Cấu trúc | Đầu vào & Biểu diễn | Hành vi thực tế trên PHP 8.4 | Điều kiện / Không nên giả định |
|---|---|---|---|
| `int` (Số nguyên) | Số nguyên 64-bit có dấu (trên hệ thống 64-bit), hệ 10, 16 (`0x`), 2 (`0b`), 8 (`0o`). | Hỗ trợ dấu gạch dưới phân tách hàng nghìn (như `1_000_000`) giúp đọc mã nguồn rõ ràng. | Khi vượt quá `PHP_INT_MAX`, PHP tự động chuyển sang kiểu `float` làm giảm độ chính xác chữ số cuối. |
| `float` (Số thực IEEE 754) | Số thực dấu phẩy động 64-bit theo chuẩn nhị phân IEEE 754 (như `0.1`, `1.5e3`). | Có độ chính xác khoảng 14 chữ số thập phân; `0.1 + 0.2` thành `0.30000000000000004`. | Không dùng `===` để so sánh hai số `float` sau phép tính; không dùng `float` để tính tiền tệ. |
| `bool` & `null` | `true`, `false` và hằng số `null` (biểu thị trạng thái không có giá trị). | Khi truyền vào `echo`, `true` in ra `"1"`, còn `false` và `null` đều bị đổi thành chuỗi rỗng `""`. | Không dùng `echo` để kiểm tra biến `bool` hay `null`; luôn dùng `var_dump()` khi gỡ lỗi. |
| Hằng số có kiểu (`const int`) | Khai báo `public const int MAX = 3;` bên trong class hoặc interface. | Bắt buộc giá trị gán vào hằng số phải khớp đúng kiểu khai báo ngay từ pha biên dịch. | Hằng số toàn cục tạo bằng `define()` truyền thống không hỗ trợ cú pháp gắn nhãn kiểu như `const` trong class. |
Khi một toán tử số học như + hoặc - nhận vào một chuỗi ký tự, PHP sẽ kích hoạt cơ chế Type Juggling dựa trên phân loại chuỗi số (Numeric Strings):
"42" hoặc "3.14": Được chuyển đổi êm thấm sang int(42) hoặc float(3.14) ngay cả khi tệp có bật strict_types=1 (vì strict_types chỉ kiểm tra chữ ký hàm, không chặn toán tử số học)."15_ban_ghi": Phát sinh cảnh báo Warning: A non-numeric value encountered, sau đó cắt bỏ phần chữ để lấy số 15 cộng tiếp."abc": Ném ngay ngoại lệ TypeError: Unsupported operand types: string + int làm dừng chương trình.Về bài toán sai số của kiểu float, do máy tính lưu số thực dưới dạng phân số nhị phân (cơ số 2), phân số thập phân 0.1 (1/10) là một số tuần hoàn vô hạn trong hệ nhị phân. Trước đây, lập trình viên phải gọi các hàm chuỗi rời rạc như bcadd('0.1', '0.2', 1). Từ PHP 8.4, tiện ích bcmath bổ sung lớp đối tượng bất biến BcMath\Number cho phép khởi tạo new Number('0.1') và cộng trừ trực tiếp bằng toán tử +, -, *, / với độ chính xác thập phân tuyệt đối.

Sơ đồ Hình 1 minh họa quy trình 4 bước an toàn khi làm việc với dữ liệu vô hướng: khai báo kiểu tường minh cho biến/hằng, dùng var_dump() để kiểm tra kiểu thực tế, ép kiểu hoặc lọc dữ liệu đầu vào trước khi tính toán.
Hãy mở terminal trong môi trường Lab, tạo thư mục tạm bằng mktemp -d và khởi tạo phần đầu của kịch bản scalar_audit.php để kiểm tra hằng số lớp có kiểu cùng hành vi của var_dump():
lab_dir="$(mktemp -d /tmp/php-ep04-XXXXXX)"
cd "$lab_dir"
cat << 'EOF' > scalar_audit.php
<?php
declare(strict_types=1);
use BcMath\Number;
class SystemQuota
{
public const int MAX_RETRIES = 3;
public const string DEFAULT_UNIT = 'GiB';
}
$active = false;
$emptyVal = null;
var_dump(SystemQuota::MAX_RETRIES, SystemQuota::DEFAULT_UNIT, $active, $emptyVal);
EOF
php scalar_audit.php
Khi chạy lệnh trên, hàm var_dump() in ra 4 dòng rõ ràng: int(3), string(3) "GiB", bool(false) và NULL. Khác với lệnh echo làm biến mất false và null, var_dump() hiển thị chính xác nhãn kiểu dữ liệu và độ dài byte của chuỗi, giúp bạn không bao giờ nhầm lẫn giữa chuỗi rỗng "", giá trị false và null.
Tiếp theo, chúng ta nối thêm đoạn mã kiểm chứng phép cộng 0.1 + 0.2 bằng kiểu float truyền thống đặt cạnh đối tượng BcMath\Number của PHP 8.4 vào tệp scalar_audit.php:
cat << 'EOF' >> scalar_audit.php
$floatSum = 0.1 + 0.2;
$isExactFloat = ($floatSum === 0.3);
$bcSum = new Number('0.1') + new Number('0.2');
$isExactBc = ($bcSum->compare(new Number('0.3')) === 0);
printf(
"Float(0.1+0.2)=%.17f | Float===0.3: %s | BcMath===0.3: %s (%s)\n",
$floatSum,
$isExactFloat ? 'true' : 'false',
$isExactBc ? 'true' : 'false',
(string) $bcSum
);
EOF
php scalar_audit.php | tail -n 1
Kết quả thực thi in ra: Float(0.1+0.2)=0.30000000000000004 | Float===0.3: false | BcMath===0.3: true (0.3). Khi định dạng tới 17 chữ số sau dấu phẩy bằng %.17f, phần đuôi sai số nhị phân 00000000000000004 lộ diện, giải thích vì sao phép so sánh === 0.3 trả về false. Ngược lại, phép cộng hai đối tượng new Number('0.1') + new Number('0.2') trên PHP 8.4 giữ nguyên giá trị thập phân '0.3' và phương thức $bcSum->compare(...) trả về 0 (bằng nhau tuyệt đối).
Để quan sát rủi ro của cơ chế Type Juggling khi dữ liệu đầu vào bị lẫn ký tự chữ sau phần số, hãy chạy lệnh kiểm thử dưới đây:
set +e
php -r 'echo "15_ban_ghi" + 5 . PHP_EOL;'
set -e
Trình thông dịch lập tức phát ra cảnh báo: Warning: A non-numeric value encountered in Command line code on line 1, rồi vẫn in ra kết quả 20. Trong ứng dụng thực tế, việc vừa sinh cảnh báo vừa tiếp tục tính toán với dữ liệu bị cắt cụt là nguồn gốc của nhiều lỗi sai lệch số liệu. Do đó, trước khi đưa chuỗi từ bên ngoài vào phép tính, bạn cần kiểm tra bằng is_numeric() hoặc filter_var($input, FILTER_VALIDATE_INT).

Hình 2 tóm tắt ranh giới sử dụng giữa kiểu float và BcMath\Number: dùng float cho các phép tính vật lý, thống kê hoặc đồ họa cần tốc độ CPU cao, và dùng số nguyên đơn vị nhỏ nhất (như xu/đồng) hoặc BcMath\Number cho bài toán tài chính. Kết thúc bài Lab, hãy xóa thư mục tạm:
cd /tmp
rm -rf "$lab_dir"
test ! -e "$lab_dir" && echo "Đã dọn sạch thư mục Lab EP 04."
Khi chuyển đổi dữ liệu chuỗi từ HTTP Request sang kiểu số trong môi trường Production, bạn cần phân biệt rõ hai công cụ:
(int) $val hoặc intval($val): Luôn trả về một số nguyên mà không bao giờ báo lỗi, kể cả khi đầu vào là chuỗi không hợp lệ (ví dụ (int) "abc" âm thầm trả về 0). Chỉ nên dùng khi bạn đã chắc chắn định dạng đầu vào hoặc chấp nhận giá trị mặc định 0.filter_var($val, FILTER_VALIDATE_INT): Kiểm tra toàn bộ chuỗi đầu vào có thực sự biểu diễn một số nguyên hợp lệ hay không; nếu truyền "15_ban_ghi" hoặc "3.14", hàm trả về false để bạn chủ động từ chối yêu cầu.public const int) từ PHP 8.3/8.4 để ngăn ngừa việc sửa nhầm hằng số số nguyên thành chuỗi khi bảo trì dự án.Hãy tự trả lời 4 câu hỏi tình huống dưới đây để củng cố kiến thức EP 4:
echo false;, màn hình lại không in ra chữ "false"?
Đáp án: Do quy tắc chuyển đổi sang chuỗi của echo. Trong PHP, echo ép kiểu đối số sang string: bool(true) thành "1", còn bool(false) và null đều thành chuỗi rỗng "". Muốn xem rõ kiểu và giá trị, phải dùng var_dump().($a + $b) === 0.3 cho hai biến kiểu float không?
Đáp án: Không. Do sai số biểu diễn nhị phân IEEE 754, phép cộng số thực có thể lệch ở chữ số thập phân thứ 16, 17; cần so sánh độ lệch tuyệt đối abs(($a + $b) - 0.3) < 1e-9 hoặc dùng BcMath\Number.declare(strict_types=1); ở đầu tệp có ngăn được cảnh báo khi thực hiện phép cộng "15_ban_ghi" + 5 không?
Đáp án: Không. strict_types=1 chỉ kiểm tra kiểu tại lời gọi hàm/trả về hàm, không thay đổi cơ chế ép kiểu của toán tử +.(bool) "0" trong PHP trả về true hay false?
Đáp án: Trả về false. Trong PHP, cả chuỗi rỗng "" lẫn chuỗi chứa một ký tự số không "0" đều được coi là false khi ép sang kiểu bool.Tiêu chí nghiệm thu: Hoàn thành EP 4 khi sử dụng thành thạo var_dump(), khai báo được hằng số lớp có kiểu, giải thích được sai số IEEE 754 và dùng được BcMath\Number trên PHP 8.4. Sang PHP EP 5, chúng ta sẽ đi sâu vào độ ưu tiên toán tử, so sánh chặt === và toán tử ??.
Ôn bài trước: PHP EP 3: Cú pháp PHP và phạm vi của strict_types. Học tiếp: PHP EP 5: Toán tử PHP và so sánh nghiêm ngặt.

Quản lý giao dịch cơ sở dữ liệu bằng beginTransaction, commit và rollBack trong PHP PDO, thực hành bật ràng buộc khóa ngoại và đóng gói truy vấn vào Repository.

Thiết kế cấu trúc thư mục dự án PHP tách biệt nghiệp vụ và giao diện, thực hành viết kịch bản kiểm thử tự động xác minh nhánh lỗi và đọc Stack Trace để gỡ lỗi.

Tổng hợp kiến thức toàn khóa qua dự án ứng dụng quản lý Ticket bằng PHP và PDO: kết hợp Property Hooks, Enum, Transaction, bảo mật biểu mẫu HTTP và bộ kiểm thử.

Phân tích vòng đời HTTP Request/Response trong PHP qua $_GET, $_POST, $_SERVER, thực hành kiểm tra dữ liệu bằng filter_var và mã hóa đầu ra chống tấn công XSS.