
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.

Khám phá lộ trình học PHP qua 6 chặng thực hành: hiểu cơ chế Zend Engine, thực thi script CLI, kiểm tra cú pháp bằng php -l và phân biệt môi trường SAPI.
Khi tìm hiểu PHP qua các đoạn mã mẫu rời rạc, nhiều lập trình viên mới thường gặp hiện tượng quen thuộc: kịch bản vài chục dòng chạy trơn tru trên máy cá nhân, nhưng khi ghép vào biểu mẫu web hoặc đưa lên máy chủ lại phát sinh lỗi khó hiểu. Có lúc biến đầu vào mang kiểu chuỗi thay vì số nguyên làm phép tính sai lệch âm thầm, có lúc trang web trắng xóa không hiện thông báo gì, hoặc lệnh chạy hoàn tất trên dòng lệnh nhưng bị ngắt giữa chừng khi gọi qua trình duyệt. Nguyên nhân cốt lõi là người học chưa tách biệt cơ chế của nhân thực thi với môi trường giao tiếp xung quanh.
Bài mở đầu thiết lập bản đồ định hướng cho lộ trình 22 bài thực hành trên chuyên mục PHP. Lấy tài liệu PHP Manual và nhánh PHP 8.4 làm mốc tham chiếu xuyên suốt, bạn sẽ nắm được cách nhân Zend Engine đọc và thực thi mã nguồn, khác biệt giữa giao diện dòng lệnh (CLI) và máy chủ web (Web SAPI), cấu trúc 6 chặng học từ nền tảng ngôn ngữ đến PDO, cùng phương pháp kiểm chứng bằng bài Lab cô lập.
Hồ sơ kiểm chứng & Môi trường Lab: Ngày kiểm chứng: Các lệnh và kịch bản mẫu trong bài được kiểm chứng ngày 26/10/2026 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à Zend OPcache v8.4.27. Bài viết tập trung vào phân tích cú pháp, thực thi CLI và định hướng lộ trình; chưa đi sâu vào PHP-FPM đa tiến trình hay kết nối mạng ngoài. Giới hạn kiểm chứng: CLI cục bộ, chưa kiểm chứng production.
Khái niệm đầu tiên cần làm rõ khi đọc tài liệu PHP là SAPI (Server Application Programming Interface). Theo PHP Command Line Usage, Zend Engine là phần lõi phân tích cú pháp, biên dịch mã thành chỉ thị máy ảo (Opcodes) và quản lý bộ nhớ, còn SAPI là lớp vỏ kết nối lõi đó với môi trường bên ngoài. Cùng một tệp .php, khi chạy qua cli SAPI sẽ có vòng đời, kênh nhập xuất và giới hạn thời gian khác hẳn khi chạy qua fpm-fcgi hoặc cli-server.
| Thành phần / Cơ chế | Đầu vào & Đầu ra chính | Hành vi thực tế trên PHP 8.4 | Điều kiện / Không nên giả định |
|---|---|---|---|
| Lexer & Parser (`php -l`) | Đầu vào: Tệp `.php`. Đầu ra: Cây AST hoặc lỗi `Parse error`. | Kiểm tra toàn bộ cú pháp tệp trước khi chạy dòng đầu tiên; trả mã thoát khác `0` nếu lỗi. | Không giả định `php -l` bắt được lỗi sai kiểu lúc chạy (`TypeError`) hoặc thiếu tệp `require`. |
| Zend Compiler & VM | Đầu vào: Cây AST. Đầu ra: Chuỗi Opcodes chạy trên Zend Engine v4.4. | Biên dịch AST thành Opcodes trong bộ nhớ rồi thực thi tuần tự từng chỉ thị. | Không mặc định OPcache bật sẵn cho CLI ngắn hạn nếu chưa đặt `opcache.enable_cli=1`. |
| Giao diện `cli` SAPI | Đầu vào: `$argv`, `STDIN`. Đầu ra: `STDOUT`, `STDERR`, mã thoát. | Chạy tiến trình độc lập cho kịch bản dòng lệnh; mặc định `max_execution_time` bằng `0`. | Không giả định có sẵn khóa HTTP như `$_SERVER['HTTP_HOST']` hoặc tự động gửi header. |
| Giao diện Web SAPI (`fpm-fcgi`, `cli-server`) | Đầu vào: `$_GET`, `$_POST`, `$_SERVER`. Đầu ra: HTTP status, headers, body. | Khởi tạo trạng thái cho từng yêu cầu HTTP và giải phóng biến toàn cục khi kết thúc. | Không giữ trạng thái người dùng trong biến toàn cục giữa hai lần tải trang; phải qua Session/DB. |
Trên Zend Engine 4.4 của PHP 8.4, quá trình thực thi luôn tách thành hai pha: pha biên dịch (Compile Time) và pha thực thi (Run Time). Ở pha biên dịch, bộ từ vựng (Lexer) tách mã thành các đơn vị từ khóa (tokens), bộ phân tích cú pháp (Parser) dựng Cây cú pháp trừu tượng (AST) và sinh chỉ thị máy ảo Opcodes. Chỉ khi toàn bộ tệp vượt qua bước sinh Opcodes không có lỗi cú pháp, máy ảo Zend VM mới thực thi dòng lệnh đầu tiên. Vì vậy lỗi thiếu dấu chấm phẩy ở cuối tệp vẫn ngăn lệnh echo ở đầu tệp chạy.
Chọn PHP 8.4 làm mốc tham chiếu giúp bạn tiếp cận trực tiếp phong cách lập trình chặt chẽ trong tài liệu phát hành PHP 8.4: khai báo kiểu nghiêm ngặt (declare(strict_types=1)), biểu thức match, Enum, nhóm hàm mảng mới (array_find(), array_find_key(), array_any(), array_all()), Property Hooks (get/set), quyền bất đối xứng public private(set) và thuộc tính #[\Deprecated].
Toàn bộ 22 bài học chia thành 6 chặng nối tiếp nhau:
php -S và kiểm soát php.ini.strict_types=1, biến, hằng số có kiểu, toán tử ===, match và Generator.mbstring và bảng băm cùng array_find.get/set), Asymmetric Visibility, Interface, Trait, Enum và PSR-4 qua Composer.Throwable, tệp và JSON, xử lý Form HTTP, chống XSS/CSRF, Session/Cookie và băm mật khẩu.
Sơ đồ Hình 1 minh họa nguyên tắc học tập: bắt đầu từ cơ chế gốc trong tài liệu chuẩn, ghi nhận phiên bản runtime, viết kịch bản nhỏ trong thư mục tạm để quan sát đầu ra, rồi chủ động thử nhánh dữ liệu sai.
Mọi bài Lab đều chạy dưới tài khoản thường (không dùng root) và tạo thư mục tạm riêng trong /tmp bằng mktemp -d. Hãy chạy khối lệnh sau để tạo vùng làm việc tạm và truy vấn phiên bản PHP cùng giao diện SAPI:
lab_dir="$(mktemp -d /tmp/php-ep01-XXXXXX)"
cd "$lab_dir"
php -v | head -n 1
php -r 'echo "SAPI=" . PHP_SAPI . " VERSION=" . PHP_VERSION . PHP_EOL;'
Trên container chuẩn, php -v | head -n 1 trả về PHP 8.4.27 (cli) (built: Oct 21 2026 23:15:14) (NTS), cho biết bản dựng chạy chế độ Non-Thread Safe (NTS). Dòng thứ hai dùng cờ -r in ra hai hằng số: PHP_SAPI là cli và PHP_VERSION là 8.4.27, xác nhận kịch bản đang chạy dưới dòng lệnh.
Tiếp theo, chúng ta tạo tệp ticket_summary.php lọc danh sách phiếu hỗ trợ và tìm phiếu đầu tiên đã đóng bằng hàm array_find() của PHP 8.4 Array Functions, kết hợp php -l để kiểm tra cú pháp độc lập:
cat << 'EOF' > ticket_summary.php
<?php
declare(strict_types=1);
$tickets = [
['id' => 101, 'title' => 'Lỗi kết nối VPN', 'open' => true],
['id' => 102, 'title' => 'Cấp quyền thư mục', 'open' => false],
['id' => 103, 'title' => 'Cấu hình máy in', 'open' => true],
];
$firstClosed = array_find(
$tickets,
static fn(array $item): bool => $item['open'] === false
);
printf(
"Tổng phiếu: %d | Đã đóng đầu tiên: #%d (%s)\n",
count($tickets),
$firstClosed['id'],
$firstClosed['title']
);
EOF
php -l ticket_summary.php
php ticket_summary.php
Kết quả xuất ra gồm hai dòng: php -l báo No syntax errors detected in ticket_summary.php, xác nhận pha phân tích AST thành công. Lệnh php ticket_summary.php in ra Tổng phiếu: 3 | Đã đóng đầu tiên: #102 (Cấp quyền thư mục). Chỉ thị declare(strict_types=1); yêu cầu kiểm tra chặt kiểu array và bool của hàm mũi tên, còn array_find() trả về ngay phần tử khớp đầu tiên.
Trong đường ống tích hợp liên tục (CI/CD), kịch bản tự động cần phát hiện lỗi cú pháp qua mã thoát ($?). Hãy tạo tệp broken_syntax.php cố tình thiếu dấu chấm phẩy ở dòng 4:
cat << 'EOF' > broken_syntax.php
<?php
declare(strict_types=1);
echo "Thiếu dấu chấm phẩy"
$x = 10;
EOF
set +e
php -l broken_syntax.php
echo "Mã thoát của php -l: $?"
set -e
Trình phân tích dừng ngay ở dòng 5 với thông báo Parse error: syntax error, unexpected variable "$x", expecting "," or ";" in broken_syntax.php on line 5 và trả mã thoát 255. Dù lệnh echo nằm ở dòng 4 phía trên vị trí lỗi, chuỗi văn bản không được in ra, chứng minh khi pha biên dịch AST thất bại thì pha thực thi không bao giờ bắt đầu.

Hình 2 đối chiếu hai môi trường thực thi: trên CLI, mã thoát 255 và thông báo trên STDERR giúp chặn bản dựng lỗi ngay lập tức; còn trên Web SAPI, lỗi cú pháp biến thành phản hồi HTTP 500. Kết thúc bài Lab, hãy dọn sạch thư mục tạm:
cd /tmp
rm -rf "$lab_dir"
test ! -e "$lab_dir" && echo "Đã dọn sạch thư mục Lab an toàn."
Khi đưa ứng dụng PHP từ container CLI lên máy chủ Production, cần lưu ý ba ranh giới:
opcache.validate_timestamps=0, cần làm mới OPcache hoặc tải lại PHP-FPM khi cập nhật mã.find src -name '*.php' -exec php -l {} + vào CI trước khi triển khai.Hãy đối chiếu 4 câu hỏi dưới đây để củng cố kiến thức EP 1:
echo "Xin chào"; ở dòng 3 nhưng thiếu dấu chấm phẩy ở dòng 50, chữ "Xin chào" có in ra không?
Đáp án: Không. Zend Engine biên dịch toàn bộ tệp thành Opcodes trước khi chạy; lỗi ở dòng 50 dừng quá trình ngay ở pha biên dịch.php -l báo No syntax errors detected có đảm bảo chạy không gặp lỗi TypeError không?
Đáp án: Không. Lệnh php -l chỉ kiểm tra cú pháp AST, không kiểm tra kiểu dữ liệu truyền vào hàm lúc chạy.mktemp -d dưới tài khoản thường khi thực hành?
Đáp án: Đúng nguyên tắc cô lập. Thư mục tạm riêng và tài khoản thường ngăn ghi đè tệp hệ thống và giúp dọn dẹp sạch sẽ.Tiêu chí nghiệm thu: Hoàn thành EP 1 khi kiểm tra được PHP 8.4.x, phân biệt rõ PHP_SAPI, dùng php -l tách lỗi cú pháp với lỗi thực thi và nắm lộ trình 6 chặng. Ở PHP EP 2, chúng ta đi sâu vào php.ini, cờ báo lỗi và php -S.
Học tiếp: PHP EP 2: Dựng lab PHP và cấu hình php.ini.

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.