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 查看本地环境下这个默认配置值:

-w651

32767 对应的错误级别是 E_ALL,这可以在所有错误级别中查询得出。

基本使用

下面举个简单的例子来测试错误报告,我们在 php_learning/oop 目录下新建一个 error.php 来存储测试代码。

上篇教程中,反序列化一个未在当前文件中定义的类时,会抛出 E_NOTICE 级别的错误,而试图访问一个不存在的 URL 链接或者除数为 0,会抛出 E_WARNING 级别的错误,我们以访问不存在的 URL 为例:

-w1028

这个时候没有配置错误报告级别,默认报告所有级别的错误,此时如果我们排除对 E_WARNING 级别错误的报告,则执行代码不会报错,同时打印函数返回的结果 false

-w1047

自定义错误处理器

另外,你还可以通过 set_error_handler 函数指定自定义错误处理器对错误进行处理,自定义处理器通常是个自定义函数,在这个函数中,我们可以自定义不同级别错误的处理逻辑:

<?php

// error_reporting(E_ALL);  // 报告所有错误(默认配置)
// error_reporting(E_ALL ^ E_WARNING);
set_error_handler("myErrorHandler");

$content = file_get_contents('https://xueyuanjun.com/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;
    }
}

运行上述代码,输出如下:

-w1110

可以看到,错误报告变成了自定义错误处理器输出的内容,并且,也不会终止程序的继续运行,因为我们并没有在处理器中退出程序。

将错误报告写入日志

我们可以通过 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 表示系统日志),第三个参数即目标值,这里是自定义的日志文件。

运行上述代码:

-w647

可以看到 STDOUT 中不再输出日志,而是写入到 oop/logs/err.log 文件中:

-w1384

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://xueyuanjun.com/error');
} catch (Error $error) {
    var_dump($error);
}
var_dump($content);

上述代码的打印结果如下:

-w941

但是需要注意的是,如果设置不报告 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 异常,关于异常捕获和处理机制的更多细节,我们将在下一篇教程中详细探讨。

上一篇: PHP 魔术方法、序列化与对象复制

下一篇: PHP 错误和异常处理(下)