Để hiện lỗi PHP, hãy đặt các dòng sau ở đầu script: ini_set('display_errors', '1'); và error_reporting(E_ALL);. Dòng đầu khiến PHP in ra thông báo lỗi, dòng thứ hai khiến nó báo mọi loại lỗi, kể cả cảnh báo (warning) và thông báo deprecation.
<?php
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
Khi đã có các dòng đó, một lỗi như đọc một biến chưa từng được gán sẽ in ra thông báo kèm tên tệp và số dòng, thay vì thất bại trong im lặng:
Warning: Undefined variable $total in /home/index.php on line 7
error_reporting() khi gọi không có đối số sẽ trả về cấu hình hiện tại dưới dạng một con số. Nhấn Chạy để đặt một mức báo lỗi và giải mã nó trở lại thành tên:
Mỗi loại lỗi là một bit, nên E_ALL & ~E_DEPRECATED có nghĩa là "mọi thứ trừ deprecation". Đổi dòng đầu thành error_reporting(E_ERROR | E_WARNING); rồi chạy lại.
Hiển thị lỗi trong php.ini
Cấu hình đặt trong script chỉ có hiệu lực khi script đã bắt đầu chạy. Để hiện lỗi cho mọi script, kể cả lỗi cú pháp, hãy sửa php.ini (tìm nó bằng php --ini):
; Development
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
Khởi động lại Apache hoặc PHP-FPM sau khi sửa. Server tích hợp php -S và dòng lệnh đọc lại tệp này mỗi lần khởi động. Trên shared hosting không có quyền truy cập php.ini, Apache với mod_php cũng chấp nhận các dòng trong .htaccess:
php_flag display_errors on
php_value error_reporting -1
-1 bật mọi bit, tương đương với E_ALL.
Các mức lỗi
| Hằng số | Nguyên nhân | Dừng script |
|---|---|---|
E_ERROR | Lỗi nghiêm trọng lúc chạy, chẳng hạn hết bộ nhớ | có |
E_PARSE | Lỗi cú pháp trong tệp | có |
E_WARNING | Biến hoặc khóa mảng chưa định nghĩa, tệp bị thiếu trong include | không |
E_NOTICE | Vấn đề nhỏ (còn rất ít trong PHP 8) | không |
E_DEPRECATED | Code mà một phiên bản PHP tương lai sẽ không chấp nhận | không |
E_USER_* | Lỗi bạn tự tạo ra bằng trigger_error() | chỉ E_USER_ERROR |
E_ALL | Mọi mức, kể cả lỗi core và lỗi biên dịch không liệt kê ở đây |
PHP 8.0 đã biến nhiều cảnh báo cũ thành ngoại lệ: chia cho 0 bằng / và truyền sai kiểu cho một hàm có sẵn giờ đều ném ra một lớp con của Error, giống như việc gọi một hàm chưa định nghĩa từ PHP 7. Bạn có thể bắt cả ba:
Ngoại lệ được trình bày ở try và catch. Cảnh báo, notice và deprecation vẫn đi qua hệ thống báo lỗi được mô tả ở đây.
Ghi log lỗi thay vì hiển thị
Trên một website đang chạy thật, người truy cập không bao giờ nên thấy thông báo lỗi: chúng để lộ đường dẫn, SQL và code. Thay vào đó, hãy ghi chúng vào một tệp log:
; Production
display_errors = Off
log_errors = On
error_log = /var/log/php/app-errors.log
error_reporting = E_ALL
Giữ error_reporting = E_ALL trên production. Hạ mức này xuống không làm lỗi biến mất, nó chỉ khiến bạn không còn nghe về chúng. Thông báo của riêng bạn cũng có thể ghi vào cùng log đó bằng error_log():
<?php
error_log("Payment failed for order 1042");
Sau đó hãy theo dõi tệp trong lúc thử nghiệm: tail -f /var/log/php/app-errors.log.
Biến cảnh báo thành ngoại lệ với set_error_handler
set_error_handler thay cách PHP xử lý mặc định cho warning, notice và deprecation bằng hàm của riêng bạn. Một mẫu hay gặp là chuyển mọi lỗi thành ErrorException, để một khóa mảng bị quên sẽ dừng code như mọi ngoại lệ khác và có thể bắt được:
Hàm xử lý nhận mức lỗi làm đối số đầu tiên, nên nó có thể xử lý từng mức theo cách khác nhau. Hàm này chỉ ghi log các deprecation và biến mọi thứ khác thành ngoại lệ:
Lỗi nghiêm trọng và lỗi cú pháp không bao giờ đến được hàm xử lý, vì script không thể tiếp tục sau chúng. Để ghi lại những lỗi đó, hãy đăng ký một hàm shutdown đọc error_get_last().
Đọc lỗi gần nhất với error_get_last
Một số hàm trả về false và chỉ báo lý do dưới dạng cảnh báo, ví dụ file_get_contents với một tệp không tồn tại. error_get_last() trả về cảnh báo đó dưới dạng mảng, rất có ích khi bạn muốn hiện thông báo của riêng mình:
Toán tử @ đặt trước lời gọi sẽ ẩn thông báo cho riêng biểu thức đó. Hãy dùng nó thật hạn chế và chỉ khi bạn kiểm tra kết quả ngay sau đó, như ở đây; một dấu @ đứng riêng chỉ che giấu bug.
Lỗi thường gặp: đã bật hiển thị lỗi nhưng trang vẫn trắng
Nếu bạn đã thêm ini_set('display_errors', '1') mà vẫn thấy trang trắng, rất có thể đó là lỗi cú pháp. PHP phân tích toàn bộ tệp trước khi chạy dòng đầu tiên, nên một dấu ; hay } bị thiếu sẽ dừng mọi thứ trước khi ini_set kịp chạy. Hãy kiểm tra tệp từ terminal, nơi thông báo luôn được in ra:
php -l index.php
Errors parsing index.php
PHP Parse error: syntax error, unexpected token "," in index.php on line 2
Số dòng chỉ đến chỗ PHP bỏ cuộc, thường là dòng ngay sau lỗi thật. Hãy sửa nó, chạy lại php -l cho đến khi thấy No syntax errors detected, rồi tải lại trang.
Câu hỏi thường gặp
Làm sao hiển thị mọi lỗi trong PHP?
Thêm ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); error_reporting(E_ALL); vào ngay đầu script, hoặc đặt display_errors = On và error_reporting = E_ALL trong php.ini rồi khởi động lại web server hoặc PHP-FPM.
Vì sao trang PHP của tôi chỉ hiện một màn hình trắng?
Một lỗi nghiêm trọng (fatal error) đã dừng script trong khi display_errors đang tắt, nên không có gì được in ra. Hãy bật hiển thị lỗi (hoặc đọc log lỗi, ví dụ bằng tail -f trên tệp mà error_log trỏ tới) để xem thông báo và số dòng.
Vì sao ini_set('display_errors', 1) không hiện lỗi cú pháp?
Lỗi cú pháp dừng PHP trước khi bất kỳ dòng nào trong tệp được chạy, nên lời gọi ini_set không bao giờ xảy ra. Hãy đặt display_errors trong php.ini, hoặc đặt ini_set trong một tệp nhỏ rồi include tệp bị lỗi từ đó.
Có nên bật display_errors trên production không?
Không. Thông báo lỗi để lộ đường dẫn tệp và chi tiết code cho người truy cập. Trên production hãy dùng display_errors = Off, log_errors = On cùng một đường dẫn error_log, và giữ error_reporting = E_ALL để các vấn đề vẫn được ghi lại.
error_reporting(0) làm gì?
Nó tắt việc báo mọi loại lỗi cho phần còn lại của script, nên cảnh báo và thậm chí lỗi nghiêm trọng đều không tạo ra thông báo nào. Cách này che giấu vấn đề chứ không sửa chúng; hãy ưu tiên ghi log lỗi thay vì tắt tiếng chúng.