要显示 PHP 错误,在脚本顶部加上这两行:ini_set('display_errors', '1'); 和 error_reporting(E_ALL);。第一行让 PHP 输出错误信息,第二行让它报告所有类型的错误,包括警告和弃用提示。
<?php
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
加上之后,像读取一个从未赋值的变量这样的错误,会输出一条带文件名和行号的信息,而不是悄无声息地失败:
Warning: Undefined variable $total in /home/index.php on line 7
不带参数调用 error_reporting() 会以数字形式返回当前设置。点击“运行”,设置一个级别并把它解析回名称:
每种错误类型占一个二进制位,所以 E_ALL & ~E_DEPRECATED 表示“除弃用提示外的所有错误”。把第一行改成 error_reporting(E_ERROR | E_WARNING);,再运行一次看看。
在 php.ini 中显示错误
脚本中的设置要等脚本开始运行后才生效。要让所有脚本都显示错误(包括解析错误),就修改 php.ini(用 php --ini 找到它的位置):
; Development
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
修改后要重启 Apache 或 PHP-FPM。php -S 内置服务器和命令行每次启动都会读取这个文件。在无法访问 php.ini 的共享主机上,使用 mod_php 的 Apache 也接受 .htaccess 中的配置:
php_flag display_errors on
php_value error_reporting -1
-1 会把所有位都设为 1,效果与 E_ALL 相同。
错误级别
| 常量 | 触发原因 | 是否终止脚本 |
|---|---|---|
E_ERROR | 运行时致命错误,比如内存耗尽 | 是 |
E_PARSE | 文件中的语法错误 | 是 |
E_WARNING | 未定义的变量或数组键,include 的文件不存在 | 否 |
E_NOTICE | 小问题(PHP 8 中已所剩无几) | 否 |
E_DEPRECATED | 未来的 PHP 版本将不再支持的代码 | 否 |
E_USER_* | 你用 trigger_error() 触发的错误 | 只有 E_USER_ERROR |
E_ALL | 所有级别,包括这里没有列出的核心错误和编译错误 |
PHP 8.0 把许多旧的警告改成了异常:用 / 除以零、给内置函数传入错误的类型,现在都会抛出 Error 的子类,就像从 PHP 7 起调用未定义的函数那样。这三种都可以被捕获:
异常的内容见 try 和 catch。警告、通知和弃用提示仍然走这里介绍的错误报告机制。
把错误写入日志而不是显示
在线上网站中,访问者永远不应该看到错误信息:它们会暴露路径、SQL 和代码。应该把错误写进日志文件:
; Production
display_errors = Off
log_errors = On
error_log = /var/log/php/app-errors.log
error_reporting = E_ALL
生产环境中要保留 error_reporting = E_ALL。调低级别并不会让错误消失,只会让你听不到它们。你自己的信息也可以用 error_log() 写进同一个日志:
<?php
error_log("Payment failed for order 1042");
测试时盯着这个文件看:tail -f /var/log/php/app-errors.log。
用 set_error_handler 把警告转换为异常
set_error_handler 用你自己的函数取代 PHP 对警告、通知和弃用提示的默认处理。一种常见写法是把它们全部转换成 ErrorException,这样忘记检查的数组键会像其他异常一样中断代码,并且可以被捕获:
处理函数的第一个参数是错误级别,所以可以对不同级别区别处理。下面这个处理函数只记录弃用提示,其他的都转换成异常:
致命错误和解析错误永远不会到达处理函数,因为出现它们之后脚本无法继续运行。要记录这类错误,就注册一个读取 error_get_last() 的关闭函数。
用 error_get_last 读取最后一个错误
有些函数会返回 false,只通过一条警告说明原因,比如对不存在的文件调用 file_get_contents。error_get_last() 以数组形式返回这条警告,想显示自己的提示信息时很有用:
调用前面的 @ 运算符会屏蔽这一个表达式的错误信息。要谨慎使用,并且只在像这里一样紧接着检查结果时使用;单独使用 @ 只会把 bug 藏起来。
常见错误:错误显示已打开,页面却仍然空白
如果你已经加了 ini_set('display_errors', '1'),看到的还是白屏,那很可能是解析错误。PHP 会在运行第一行之前解析整个文件,所以少了一个 ; 或 },在 ini_set 运行之前一切就停了。在终端里检查这个文件,终端总会输出错误信息:
php -l index.php
Errors parsing index.php
PHP Parse error: syntax error, unexpected token "," in index.php on line 2
行号指向 PHP 放弃解析的位置,这往往是真正出错位置的下一行。修好后再次运行 php -l,直到它显示 No syntax errors detected,然后刷新页面。
常见问题
PHP 中怎么显示所有错误?
在脚本最顶部加上 ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); error_reporting(E_ALL);,或者在 php.ini 中设置 display_errors = On 和 error_reporting = E_ALL,然后重启 Web 服务器或 PHP-FPM。
为什么我的 PHP 页面一片空白?
在 display_errors 关闭的情况下,一个致命错误终止了脚本,所以什么都没有输出。打开错误显示(或者查看错误日志,比如对 error_log 指向的文件执行 tail -f),就能看到错误信息和行号。
为什么 ini_set('display_errors', 1) 不显示我的解析错误?
解析错误会在文件的任何一行运行之前就让 PHP 停下来,所以 ini_set 根本没有执行。可以在 php.ini 中设置 display_errors,或者把 ini_set 写进一个小文件,再由它 include 出错的文件。
生产环境应该打开 display_errors 吗?
不应该。错误信息会向访问者暴露文件路径和代码细节。生产环境中使用 display_errors = Off、log_errors = On 并设置 error_log 路径,同时保留 error_reporting = E_ALL,这样问题仍会被记录下来。
error_reporting(0) 有什么作用?
它会在脚本剩余部分关闭所有类型错误的报告,所以警告甚至致命错误都不会产生任何信息。这只是把问题藏起来,并没有解决问题;与其屏蔽错误,不如把错误记录到日志。