Chuyển đến nội dung chính (Skip to content)
Mục lục bài viết
PHP

PHP EP 1: Lộ trình học PHP từ CLI đến ứng dụng web

PHP EP 1: Lộ trình học PHP từ CLI đến ứng dụng web

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.

Học PHP từ ví dụ nhỏ đến ứng dụng thực tế

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.

CLI và Web SAPI có hợp đồng thực thi khác nhau

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.

Zend Engine thực thi mã PHP như thế nào

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].

Lộ trình PHP gồm 6 chặng thực hành

Toàn bộ 22 bài học chia thành 6 chặng nối tiếp nhau:

  • Chặng 0 (EP 1 đến EP 2): Kiến trúc thực thi PHP 8.4, thiết lập CLI, máy chủ php -S và kiểm soát php.ini.
  • Chặng 1 (EP 3 đến EP 7): Quy tắc cú pháp, strict_types=1, biến, hằng số có kiểu, toán tử ===, match và Generator.
  • Chặng 2 (EP 8 đến EP 10): Union/Intersection Types, Closure, xử lý tiếng Việt UTF-8 bằng mbstring và bảng băm cùng array_find.
  • Chặng 3 (EP 11 đến EP 14): Class, Object, Constructor Promotion, Property Hooks (get/set), Asymmetric Visibility, Interface, Trait, Enum và PSR-4 qua Composer.
  • Chặng 4 (EP 15 đến EP 18): Cây ngoại lệ Throwable, tệp và JSON, xử lý Form HTTP, chống XSS/CSRF, Session/Cookie và băm mật khẩu.
  • Chặng 5 (EP 19 đến EP 22): PDO Prepared Statements chống SQL Injection, Transaction, kiểm thử tự động và ứng dụng Ticket ở EP 22.
Luồng 4 bước học PHP có kiểm chứng từ câu hỏi cơ chế đến thực thi trong vùng tạm.
Hình 1. Luồng 4 bước học PHP có kiểm chứng từ câu hỏi cơ chế đến thực thi trong vùng tạm. Sơ đồ minh họa khái niệm; không phải ảnh chụp hay bản ghi kiểm thử. Mở hình để xem chi tiết.

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.

Kiểm tra PHP CLI trong môi trường cô lập

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.

Viết script đầu tiên và kiểm tra cú pháp

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.

Tạo lỗi cú pháp có kiểm soát và dọn dẹp

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.

Đối chiếu môi trường thực thi PHP CLI độc lập với mô hình xử lý yêu cầu HTTP trên Web.
Hình 2. Đối chiếu môi trường thực thi PHP CLI độc lập với mô hình xử lý yêu cầu HTTP trên Web. Sơ đồ minh họa khái niệm; không phải ảnh chụp hay bản ghi kiểm thử. Mở hình để xem chi tiết.

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."

Giới hạn khi chuyển từ CLI lên production

Khi đưa ứng dụng PHP từ container CLI lên máy chủ Production, cần lưu ý ba ranh giới:

  1. Bộ nhớ đệm mã máy ảo (Zend OPcache): Tiện ích OPcache lưu chuỗi Opcodes trong bộ nhớ chia sẻ để tránh đọc lại ổ đĩa ở mỗi yêu cầu HTTP. Nếu đặt 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ã.
  2. Vòng đời tiến trình dài hạn: Tác vụ CLI chạy nền nhiều giờ không tự giải phóng biến sau mỗi yêu cầu như Web SAPI, nên cần chủ động giải phóng mảng lớn và đóng kết nối khi xong việc.
  3. Kiểm tra cú pháp tự động: Kết hợp find src -name '*.php' -exec php -l {} + vào CI trước khi triển khai.

Tự kiểm tra và giới hạn

Hãy đối chiếu 4 câu hỏi dưới đây để củng cố kiến thức EP 1:

  • Câu hỏi 1: Nếu tệp PHP có 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.
  • Câu hỏi 2: Lệnh 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.
  • Câu hỏi 3: Có thể dùng biến toàn cục để đếm tổng lượt truy cập giữa các lần tải trang trên Web SAPI không? Đáp án: Không. Biến của mỗi yêu cầu HTTP bị thu hồi ngay khi phản hồi kết thúc; dữ liệu bền vững phải lưu ở DB, cache hoặc tệp.
  • Câu hỏi 4: Vì sao luôn tạo thư mục bằng 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.

Bài liên quan trong bộ PHP

Tìm hiểu thêm