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

PHP EP 2: Dựng lab PHP và cấu hình php.ini

PHP EP 2: Dựng lab PHP và cấu hình php.ini

Hướng dẫn dựng môi trường Lab PHP cô lập: tra cứu tệp cấu hình bằng php --ini, điều chỉnh cờ báo lỗi, chạy Built-in Server và phân biệt với máy chủ thật.

Vì sao PHP hiện lỗi ở máy này nhưng im lặng ở máy khác

Một tình huống gây bối rối phổ biến khi vận hành và phát triển ứng dụng PHP là sự sai lệch hành vi giữa các môi trường: cùng một đoạn mã gọi hàm hoặc truy cập khóa mảng, trên máy trạm của lập trình viên thì hiển thị cảnh báo rõ ràng ngay trên trình duyệt, nhưng khi đưa vào container thử nghiệm hoặc máy chủ nội bộ lại trả về trang trắng hoặc âm thầm bỏ qua mà không rõ tệp nhật ký (log) nằm ở đâu. Ngược lại, có những hệ thống vô tình bật hiển thị chi tiết đường dẫn tệp và cấu trúc truy vấn ra màn hình công khai khi gặp sự cố. Mọi khác biệt này đều bắt nguồn từ cách trình thông dịch PHP nạp tệp cấu hình php.ini và cách thiết lập các chỉ thị điều khiển lỗi.

Bài học thứ hai trong lộ trình PHP nối tiếp kiến thức về SAPI ở PHP EP 1 để giúp bạn làm chủ môi trường thực hành. Dựa trên tài liệu chính thức về Tệp cấu hình php.ini và Built-in Web Server của nhánh PHP 8.4, bạn sẽ học cách tra cứu chính xác tệp cấu hình đang được nạp bằng php --ini, ghi đè chỉ thị tạm thời bằng cờ -d trên dòng lệnh, phân biệt ranh giới giữa display_errors và log_errors, đồng thời khởi chạy máy chủ HTTP tích hợp sẵn (php -S) trên giao diện loopback để kiểm thử nhanh ứng dụng web.

Hồ sơ kiểm chứng & Môi trường Lab: Ngày kiểm chứng: Toàn bộ lệnh, chỉ thị cấu hình và yêu cầu HTTP loopback trong bài đã được kiểm chứng thực tế vào ngày 26/10/2026 bên trong container Docker cô lập mạng ngoài (--network none), hệ thống 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à công cụ curl. Bài viết chỉ kiểm chứng máy chủ tích hợp sẵn php -S trên địa chỉ loopback 127.0.0.1; không kiểm thử cấu hình Nginx/Apache đứng trước PHP-FPM hoặc chứng chỉ TLS. Giới hạn kiểm chứng: CLI cục bộ, chưa kiểm chứng production.

Các tùy chọn cấu hình PHP cần phân biệt

Khi khởi động, trình thông dịch PHP tìm kiếm tệp php.ini tại đường dẫn biên dịch mặc định và thư mục cấu hình bổ sung (conf.d). Theo tài liệu Where a configuration setting may be set, mỗi chỉ thị trong PHP có phạm vi thay đổi khác nhau: có chỉ thị cho phép đổi ngay trong mã nguồn bằng hàm ini_set() (PHP_INI_ALL), nhưng có những chỉ thị bảo mật hoặc cấp hệ thống chỉ được phép đặt trong tệp php.ini hoặc qua cờ -d lúc khởi chạy tiến trình (PHP_INI_SYSTEM).

Chỉ thị / Công cụ Đầ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
`php --ini` Đầu vào: Lệnh CLI. Đầu ra: Đường dẫn `php.ini` chính và các tệp `.ini` bổ sung. Liệt kê chính xác tệp cấu hình đang được nạp cho giao diện CLI hiện tại; có thể báo `(none)` nếu dùng mặc định. Không giả định tệp `php.ini` của CLI giống hệt tệp `php.ini` của dịch vụ `php-fpm` trên cùng máy chủ.
`error_reporting` Đầu vào: Mặt nạ bit hằng số lỗi (như `E_ALL`). Đầu ra: Mức lọc sự kiện lỗi của Zend Engine. Từ PHP 8.0 trở đi và trên PHP 8.4, giá trị mặc định là `E_ALL` (`30719`), bao gồm cả cảnh báo `E_DEPRECATED`. Chỉ lọc loại lỗi nào được kích hoạt; không quyết định lỗi đó sẽ in ra màn hình hay ghi vào tệp log.
`display_errors` & `log_errors` Đầu vào: Cờ bật/tắt (`1`/`0`). Đầu ra: Luồng xuất màn hình hoặc tệp tại `error_log`. `display_errors=1` in lỗi ra luồng xuất chuẩn; `log_errors=1` đẩy thông điệp lỗi vào `STDERR` hoặc tệp `error_log`. Tuyệt đối không bật `display_errors=1` trên máy chủ Production vì làm lộ đường dẫn nội bộ và cấu trúc hệ thống.
`php -S 127.0.0.1:PORT -t DIR` Đầu vào: Địa chỉ IP, cổng và thư mục gốc (`-t`). Đầu ra: Máy chủ HTTP SAPI `cli-server`. Khởi chạy máy chủ web đơn luồng phục vụ tệp tĩnh và chuyển yêu cầu động vào kịch bản định tuyến. Chỉ dùng cho phát triển và kiểm thử cục bộ; một yêu cầu bị treo sẽ chặn toàn bộ các yêu cầu kế tiếp.

PHP nạp cấu hình và ghi lỗi như thế nào

Trong container tối giản, khi chạy php --ini, dòng Loaded Configuration File có thể hiển thị (none). Điều đó nghĩa là PHP đang dùng bộ giá trị mặc định biên dịch trong mã nguồn C của PHP 8.4.27. Khi cần thay đổi thông số cho riêng một lần chạy thử nghiệm, sửa trực tiếp /usr/local/etc/php/php.ini đòi hỏi quyền quản trị và ảnh hưởng mọi tiến trình khác. Thay vào đó, cờ -d ten_chi_thi=gia_tri áp dụng cấu hình cô lập cho duy nhất tiến trình vừa gọi.

Nguyên lý quan trọng nhất trong PHP Error Handling là tách bạch mức độ bắt lỗi (error_reporting) với kênh xuất lỗi (display_errors và log_errors). Trên cả môi trường phát triển lẫn Production, luôn giữ error_reporting = E_ALL để không bỏ sót cảnh báo E_DEPRECATED hay E_WARNING. Khi viết code trong Lab, bật display_errors=1 để thấy lỗi ngay; nhưng trên Production, phải đặt display_errors=0 kết hợp log_errors=1 và chỉ định error_log để ghi nhận sự cố kín đáo vào tệp nhật ký.

Quy trình 4 bước kiểm tra phiên bản, tệp cấu hình và cờ báo lỗi trước khi thực hành.
Hình 1. Quy trình 4 bước kiểm tra phiên bản, tệp cấu hình và cờ báo lỗi trước khi thực hành. 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 1 tóm tắt 4 bước chuẩn bị môi trường trước mỗi bài thực hành: kiểm tra phiên bản runtime, xác định tệp cấu hình, kiểm soát cờ báo lỗi bằng -d và chạy máy chủ loopback khi thử HTTP.

Tìm php.ini và điều chỉnh báo lỗi trong lab

Chúng ta bắt đầu bài thực hành trong thư mục tạm dưới tài khoản phplab, kiểm tra đường dẫn cấu hình hiện tại và thử ghi đè chỉ thị báo lỗi bằng cờ -d kết hợp ini_get() / ini_set():

lab_dir="$(mktemp -d /tmp/php-ep02-XXXXXX)"
cd "$lab_dir"
php --ini | head -n 2
php -d display_errors=1 -d error_reporting=E_ALL -r '
ini_set("log_errors", "1");
printf("display_errors=%s error_reporting=%s\n", ini_get("display_errors"), ini_get("error_reporting"));
'

Kết quả hiển thị Configuration File (php.ini) Path: /usr/local/etc/php và Loaded Configuration File: (none). Ở lệnh thứ hai, dòng xuất display_errors=1 error_reporting=30719 xác nhận hai chỉ thị truyền qua cờ -d đã có hiệu lực ngay, với 30719 là giá trị thập phân của mặt nạ bit E_ALL trên PHP 8.4.

Chạy built-in server trên địa chỉ loopback

Để học xử lý yêu cầu web mà chưa cần cài Nginx hay Apache, PHP cung cấp máy chủ tích hợp qua cờ -S (SAPI cli-server). Nguyên tắc an toàn là luôn gắn máy chủ vào địa chỉ nội bộ 127.0.0.1 và trỏ thư mục gốc (-t) vào thư mục con public để tránh lộ tệp cấu hình ở thư mục cha.

Hãy tạo public/index.php kiểm tra đường dẫn $_SERVER['REQUEST_URI'], khởi chạy máy chủ ngầm trên cổng 18080 và dùng curl truy vấn:

mkdir -p public
cat << 'EOF' > public/index.php
<?php
declare(strict_types=1);

$uri = $_SERVER['REQUEST_URI'] ?? '/';
if ($uri === '/health') {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(['status' => 'ok', 'sapi' => PHP_SAPI], JSON_UNESCAPED_UNICODE) . "\n";
    return;
}

http_response_code(404);
header('Content-Type: text/plain; charset=utf-8');
echo "Không tìm thấy đường dẫn: " . $uri . "\n";
EOF

php -S 127.0.0.1:18080 -t public > "$lab_dir/server.log" 2>&1 &
srv_pid=$!
sleep 0.3
curl -sS -w '\nHTTP_CODE=%{http_code}\n' http://127.0.0.1:18080/health

Khi truy vấn /health, máy chủ trả về JSON {"status":"ok","sapi":"cli-server"} kèm HTTP_CODE=200. Hằng số PHP_SAPI lúc này chuyển thành cli-server và hàm header() đặt kiểu nội dung application/json.

Kiểm tra HTTP 404, nhật ký lỗi và dọn dẹp

Tiếp theo chúng ta thực hiện hai phép kiểm thử nhánh lỗi: gửi yêu cầu tới đường dẫn không tồn tại (/missing) để xác minh mã 404, và tắt hiển thị lỗi màn hình (display_errors=0) nhưng bật ghi nhật ký vào tệp riêng (error_log):

curl -sS -w '\nHTTP_CODE=%{http_code}\n' http://127.0.0.1:18080/missing

php -d display_errors=0 -d log_errors=1 -d error_log="$lab_dir/php-error.log" -r '
trigger_error("Cảnh báo thử nghiệm ghi vào tệp log", E_USER_WARNING);
'
cat "$lab_dir/php-error.log"

Lệnh curl đầu tiên trả về Không tìm thấy đường dẫn: /missing cùng HTTP_CODE=404. Ở lệnh thứ hai, trigger_error() phát sinh cảnh báo E_USER_WARNING, màn hình chuẩn không in ra lỗi nhờ display_errors=0, còn tệp $lab_dir/php-error.log ghi nhận đầy đủ: PHP Warning: Cảnh báo thử nghiệm ghi vào tệp log in Command line code on line 2.

Ranh giới giữa Built-in Web Server đơn luồng khi học tập và kiến trúc PHP-FPM thực tế.
Hình 2. Ranh giới giữa Built-in Web Server đơn luồng khi học tập và kiến trúc PHP-FPM thực tế. 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 máy chủ php -S trong Lab với mô hình triển khai thực tế: php -S đơn luồng gọn nhẹ để học tập, nhưng không dùng để chịu tải ngoài mạng. Kết thúc bài Lab, hãy dừng tiến trình máy chủ bằng tín hiệu TERM và xóa thư mục tạm:

kill -TERM "$srv_pid" 2>/dev/null || true
wait "$srv_pid" 2>/dev/null || true
cd /tmp
rm -rf "$lab_dir"
test ! -e "$lab_dir" && echo "Đã dừng Built-in Server và dọn sạch Lab."

Cấu hình PHP-FPM và nhật ký trên production

Trên môi trường Production dùng Nginx và PHP-FPM, cần lưu ý ba điểm mở rộng:

  1. Tách biệt tệp cấu hình theo SAPI: Trên Debian/Ubuntu, /etc/php/8.4/cli/php.ini và /etc/php/8.4/fpm/php.ini độc lập nhau. Khi đổi memory_limit hay upload_max_filesize cho web, phải sửa ở nhánh fpm và tải lại php8.4-fpm.
  2. Khóa ghi đè bằng php_admin_flag: Trong cấu hình pool (www.conf), đặt php_admin_flag[display_errors] = off sẽ ngăn mã nguồn ứng dụng tự bật lại bằng ini_set().
  3. Thu thập nhật ký qua STDERR: Trong container sản xuất, error_log thường trỏ về /proc/self/fd/2 để hệ thống giám sát thu thập.

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

Hãy tự đối chiếu 4 câu hỏi dưới đây:

  • Câu hỏi 1: Sửa tệp liệt kê bởi php --ini có chắc chắn làm đổi cấu hình của dịch vụ php-fpm không? Đáp án: Không. Lệnh php --ini chỉ hiển thị cấu hình của cli; dịch vụ php-fpm đọc tệp php.ini và cấu hình pool riêng.
  • Câu hỏi 2: Đặt display_errors=0 trên Production có làm mất nhật ký lỗi không? Đáp án: Không. Khi giữ error_reporting=E_ALL và log_errors=1, mọi lỗi vẫn ghi đầy đủ vào error_log mà không lộ ra trình duyệt.
  • Câu hỏi 3: Có nên dùng php -S 0.0.0.0:80 chạy website chính thức ngoài Internet không? Đáp án: Tuyệt đối không. Built-in Web Server đơn luồng chỉ dành cho phát triển cục bộ; một yêu cầu chậm sẽ chặn toàn bộ người dùng khác.
  • Câu hỏi 4: Vì sao chạy php -S nên dùng -t public thay vì trỏ vào thư mục gốc dự án? Đáp án: Để cô lập tệp công khai. Chỉ đặt index.php và tài sản tĩnh trong public giúp ngăn truy cập trực tiếp tệp cấu hình hoặc nhật ký ở thư mục cha.

Tiêu chí nghiệm thu: Hoàn thành EP 2 khi tra cứu được php --ini, dùng cờ -d tách biệt hiển thị lỗi và ghi log, khởi chạy và dừng sạch php -S trên 127.0.0.1. Sang PHP EP 3, chúng ta sẽ khảo sát cú pháp thẻ mở và declare(strict_types=1).

Ôn bài trước: PHP EP 1: Lộ trình học PHP từ CLI đến ứng dụng web. Học tiếp: PHP EP 3: Cú pháp PHP và phạm vi của strict_types.

Bài liên quan trong bộ PHP

Tìm hiểu thêm