PHP 错误和异常处理(上)
错误报告
设置错误级别
在 PHP 5 中,程序错误会被划分为多种级别:https://www.php.net/manual/zh/errorfunc.constants.php,然后可以通过 error_reporting 函数设置报告的错误级别:
error_reporting(E_ALL); // 报告所有 PHP 错误
error_reporting(0); // 关闭所有 PHP 错误报告
error_reporting(-1); // 与上面?操作相反,也是报告所有 PHP 错误
当然,更常见的是通过位运算 报告特定级别的错误:
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
要排除对 E_NOTICE
级别的错误报告可以这么做:
error_reporting(E_ALL ^ E_NOTICE);
如果没有在 PHP 应用程序中调用 error_reporting
设置错误报告级别,则会应用 PHP 全局配置文件 php.ini
中默认的错误报告级别。我们可以在命令行通过 php -i | grep error_reporting
查看本地环境下这个默认配置值:
32767
对应的错误级别是 E_ALL
,这可以在所有错误级别中查询得出。
基本使用
下面举个简单的例子来测试错误报告,我们在 php_learning/oop
目录下新建一个 error.php
来存储测试代码。
在上篇教程中,反序列化一个未在当前文件中定义的类时,会抛出 E_NOTICE
级别的错误,而试图访问一个不存在的 URL 链接或者除数为 0,会抛出 E_WARNING
级别的错误,我们以访问不存在的 URL 为例:
这个时候没有配置错误报告级别,默认报告所有级别的错误,此时如果我们排除对 E_WARNING
级别错误的报告,则执行代码不会报错,同时打印函数返回的结果 false
:
自定义错误处理器
另外,你还可以通过 set_error_handler 函数指定自定义错误处理器对错误进行处理,自定义处理器通常是个自定义函数,在这个函数中,我们可以自定义不同级别错误的处理逻辑:
<?php
// error_reporting(E_ALL); // 报告所有错误(默认配置)
// error_reporting(E_ALL ^ E_WARNING);
set_error_handler("myErrorHandler");
$content = file_get_contents('https://laravel.geekai.co/error');
var_dump($content);
/**
* 自定义错误处理器
* @param $errno int 错误级别
* @param $errstr string 错误信息
* @param $errfile string 错误文件
* @param $errline int 错误行号
*/
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
// 该级别错误不报告的话退出
if (!(error_reporting() & $errno)) {
return;
}
switch ($errno) {
case E_ERROR:
echo "致命错误类型: [$errno] $errstr\n";
break;
case E_WARNING:
echo "警告错误类型: [$errno] $errstr\n";
break;
case E_NOTICE:
echo "一般错误类型: [$errno] $errstr\n";
break;
default:
echo "未知错误类型: [$errno] $errstr\n";
break;
}
}
运行上述代码,输出如下:
可以看到,错误报告变成了自定义错误处理器输出的内容,并且,也不会终止程序的继续运行,因为我们并没有在处理器中退出程序。
将错误报告写入日志
我们可以通过 set_error_handler
函数定义一个全局的自定义错误处理机制,另外,错误报告默认输出到标准输出 STDOUT 中了,我们还可以通过 error_log 函数将其输出到指定日志文件:
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
...
$logDir = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
if (!file_exists($logDir)) {
mkdir($logDir);
}
$logFile = $logDir . DIRECTORY_SEPARATOR . 'err.log';
switch ($errno) {
case E_ERROR:
error_log("致命错误: [$errno] $errstr", 3, $logFile);
break;
case E_WARNING:
error_log("警告: [$errno] $errstr", 3, $logFile);
break;
case E_NOTICE:
error_log("通知: [$errno] $errstr", 3, $logFile);
break;
default:
echo "未知错误类型: [$errno] $errstr\n";
break;
}
}
在写入指定日志文件之前,先通过 PHP 文件系统函数 创建对应的日志目录(运行 PHP 脚本所在目录下创建 logs
子目录),生成的日志将存放在该目录下,然后在写入日志函数 error_log
中,第一个参数是错误消息,第二个参数是写入目标(3 表示指定文件,1 表示邮箱,0 表示系统日志),第三个参数即目标值,这里是自定义的日志文件。
运行上述代码:
可以看到 STDOUT 中不再输出日志,而是写入到 oop/logs/err.log
文件中:
Error 异常
不同于 PHP 5 的错误报告机制,在 PHP 7 中,大多数错误被作为 Error 异常抛出,这种 Error 异常可以像 Exception 那样被捕获,如果没有对 Error 异常进行捕获,则调用全局异常处理器(通过 set_exception_handler 函数注册)处理,如果全局异常处理器也没有注册,则按照传统错误报告方式处理,就像我们上面演示的那样,如果通过 try/catch
语句捕获,对应捕获代码如下:
<?php
// error_reporting(E_ALL); // 报告所有错误(默认配置)
// error_reporting(E_ALL ^ E_WARNING);
// set_error_handler("myErrorHandler");
try {
$content = file_get_contents('https://laravel.geekai.co/error');
} catch (Error $error) {
var_dump($error);
}
var_dump($content);
上述代码的打印结果如下:
但是需要注意的是,如果设置不报告 WARNING
级别的错误,则不会抛出 Error
异常,另外,如果通过 set_error_handler
设置了自定义错误处理器,则优先应用该配置,也不会抛出 Error 异常。
另外,和传统错误报告一样,你可以通过设置 display_errors
选项决定是否向用户显示错误报告和 Error 异常,该配置默认在 PHP 配置文件中全局设置,你也可以通过 ini_set 在运行时设置:
ini_set('display_errors', 0);
该值默认为 1,表示显示用户级错误,设置为 0 则表示不显示用户级错误,你可以自行测试下设置与否对错误输出的影响。还有一个与之类似的全局配置 display_startup_errors
,表示是否显示 PHP 启动过程中的错误信息,设置逻辑也是一样。建议在线上环境将这两个配置值都设置为 0。
和其他 PHP 异常类型不同,Error
异常和 Exception
类并不是父子关系,而是兄弟关系,所以不能通过 Exception 捕获 Error 异常,关于异常捕获和处理机制的更多细节,我们将在下一篇教程中详细探讨。
无评论