PHP 中 Error 和 Exception 两种异常的特性及日志记录或显示

本文详细探讨了PHP7中异常机制的使用,包括Error和Exception的区别,如何在开发和生产环境中正确处理错误,以及如何自定义错误处理程序。介绍了如何通过try/catch块捕获Error对象,以及如何设置PHP配置文件来控制错误的报告和处理。

PHP 文档:
Error
Exception

参考:
深入理解PHP原理之异常机制
我们什么时候应该使用异常
异常和错误

所有示例基于 PHP7。

应用中,关于错误的最佳实践是:

  • 必须报告错误
  • 开发环境要显示错误,生产环境不可显示
  • 开发环境和生产环境都要记录错误日志

Error 和 Exception 的异同

  • Exception 需要通过 throw new Exception 手动抛出
  • Error 可以在 PHP 脚本执行发生错误时自动触发,也可以通过 trigger_errors() 手动触发
  • 都实现了 Throwable 接口,可以通过 catch (Throwable $t) {...} 同时捕获 Error 和 Exception
  • 如果不捕获并处理 Exception,程序会终止,并报出 Fatal Error 错误,但捕获后程序可以继续执行
  • catch (Error $e) { ... },或者通过注册错误处理函数( set_error_handler())来捕获 Error
  • catch (Exception $e) { ... } 或者通过注册异常处理函数( set_exception_handler())来捕获 Exception
  • catch (Throwable $e) { ... } 可以同时捕获 Exception 和 Error
<?php

echo 1/0;
echo 666;
echo 1%0;
echo 666;
PHP Warning: Division by zero in /code/main.php on line 3
INF666
PHP Fatal error:  Uncaught DivisionByZeroError: Modulo by zero in /code/main.php:5

Throwable

用户定义的类无法实现 Throwable,所以用户只能抛出 Exception 或 Error 的实例。扩展 Throwable 的接口只能通过扩展 Exception 或 Error 的类来实现。

继承关系

Error 类 和 Exception 类 都继承自 Throwable 接口,不同版本的继承关系可以参考 这里。下面是 7.2.0 - 7.2.7 的继承关系:

Error
   ArithmeticError
      DivisionByZeroError
   AssertionError
   ParseError
   TypeError
      ArgumentCountError
Exception
   ClosedGeneratorException
   DOMException
   ErrorException
   IntlException
   LogicException
      BadFunctionCallException
         BadMethodCallException
      DomainException
      InvalidArgumentException
      LengthException
      OutOfRangeException
   PharException
   ReflectionException
   RuntimeException
      OutOfBoundsException
      OverflowException
      PDOException
      RangeException
      UnderflowException
      UnexpectedValueException
   SodiumException

常用方法

完整的接口可以参考 这里

  • getMessage — 获取异常消息内容
  • getCode — 获取异常代码
  • getFile — 导致异常的程序文件名称
  • getLine — 导致异常的行号

Error

从 PHP 7 开始,大多数错误(致命错误和可恢复错误)被作为 Error 异常抛出,从而可以捕获并处理,防止脚本终止执行。与任何其他 Exception 异常一样,可以使用 try / catch 块捕获 Error 对象。

从致命(fatal)和可恢复(recoverable)的错误中抛出的异常并没有继承 Exception,而是继承自 Error。

Error 的严重等级

Parse error > Fatal Error > Waning > Notice > Deprecated

错误名称解释可能的原因程序是否中止如何捕获错误备注
Parse error语法错误代码解析失败中断执行PHP7 之后可以用 catch (Error $e) { ... } 捕获
Fatal Error运行时错误实例化不存在的类,调不存在的方法中断执行PHP7 之后可以用 catch (Error $e) { ... } 捕获可以使用 register_shutdown_function() 函数设置一个在 PHP 中止前执行收尾工作的函数
Waning警告四则运算时出现非数字继续执行可以用 set_error_handler() 捕获
Notice注意变量或数组下标未定义继续执行可以用 set_error_handler() 捕获
Deprecated使用了废弃函数函数已经废弃继续执行可以用 set_error_handler() 捕获

Error 处理流程

  1. 先看看有没有匹配的 catch 块(注意是 Error 类型而不是 Exception 类型:catch (Error $e) { ... }),如果有则被第一个匹配的 try / catch 块所捕获。
  2. 如果没有没有匹配的 catch 块,则去调用异常处理函数(事先通过 set_error_handler() 注册)进行处理(仅用于 Deprecated、Notice、Waning 这三种级别)。
  3. 如果尚未注册异常处理函数,则按照传统方式处理:报告错误(Fatal Error 等)。

PHP 生成的每个错误都包含一个类型。类型列表以及它们的行为及其产生方式的简短描述可以参考 这里。常用的有:

常量说明
1E_ERROR致命的运行时错误。不可捕捉,不可恢复。脚本终止运行。
2E_WARNING运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
256E_USER_ERROR用户产生的错误信息。类似 E_ERROR, 但是是由用户自己在代码中使用函数 trigger_error() 触发的。
512E_USER_WARNING用户产生的警告信息。类似 E_WARNING, 但是是由用户自己在代码中使用PHP函数 trigger_error() 触发的。
2048E_STRICT (integer)启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
4096E_RECOVERABLE_ERROR可被捕捉的致命错误。它表示发生了一个可能非常危险的错误,但是还没有导致 PHP 引擎处于不稳定的状态。如果该错误没有被用户自定义处理程序捕获(set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。
8192E_DEPRECATED运行时通知。对在未来版本中可能无法正常工作的代码给出警告。
30719E_ALLE_STRICT 外的所有错误和警告信息。

设置 PHP 配置文件来处理错误

设置报告错误的等级

如果未设置错误处理程序,则 PHP 将根据 php.ini 配置文件处理发生的任何错误。error_reporting 指令控制报告和忽略哪些错误。虽然也可以在运行时通过调用 error_reporting() 函数来控制,但强烈建议设置配置指令,因为在脚本开始执行之前也可能会发生一些错误。

在开发环境中,为了了解并解决 PHP 引发的问题,最好将 error_reporting 设置为 E_ALL 来记录所有的错误。生产环境中,可以将 error_reporting 设置为 E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED 来避免记录过多信息,但是在多数情况因为下 E_ALL 可以提供早期预警,记录潜在的问题,也可以用于生产环境。

显示错误或记录日志

发生错误时,PHP 可以采取两种措施,由另外两个 php.ini 指令设置:

  • display_errors:输出错误。生产环境中必须禁用,因为它可能包含机密信息(如数据库密码),但可用于开发环境,确保立即报告问题。
  • log_errors:记录错误日志。这会将任何错误记录到 error_log 定义的文件或 syslog 中。这在生产环境中非常有用,可以记录发生的错误,然后根据这些错误生成报告。

用户自定义错误处理程序

如果 PHP 的默认错误处理不满足需求,还可以使用 set_error_handler() 安装自己的自定义错误处理程序来处理许多类型的错误。

一般用于处理用户通过 trigger_error 触发的错误,大部分 PHP 内置错误类型无法以这种方式处理。可以按照脚本认为合适的方式处理那些可以处理的错误类型:例如,向用户显示自定义错误页面,然后直接发送电子邮件报告错误,而不是通过日志。

set_error_handler('myErrorHandler');

function myErrorHandler($severity, $message, $filepath, $line) {
    echo "错误信息:".$message;
    // 发送电子邮件...
    exit(1); // 必要时手动终止脚本
}

function myDiv($a, $b) {
    return $a/$b;
}

myDiv(1, 0);
eval('ech 66'); // 无法用自定义的错误处理程序

将 Error 变为 ErrorException:

set_error_handler('myErrorHandler');
set_exception_handler('myExceptionHandler');

function myExceptionHandler($exception) {
    echo $exception->getMessage();
}
function myErrorHandler($severity, $message, $file, $line)
{
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting, so let it fall
        // through to the standard PHP error handler
        return false;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}

function myDiv($a, $b) {
    return $a/$b;
}

myDiv(1, 0);

具体的 Error 类

ArithmeticError 算术错误

两种可能的原因:

  • 使用负数移位
  • 调用 intdiv() 方法时,分子是 PHP_INT_MIN 且分母为 -1(此时将返回浮点数)。
try {
    $value = 1 << -1;
    intdiv(PHP_INT_MIN, -1);
} catch (ArithmeticError $e) {
    echo $e->getMessage(), "\n";
}

DivisionByZeroError

两种可能的原因:

  • 模数(%)运算时,分母为 0。
  • 调用 intdiv() 方法时,分母为 0。

注意,在除法(/)运算符中使用零做分母仅发出警告。

try {
    echo 1/0; // 仅警告
    intdiv(1, 0);
    echo 1%0;
} catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}

AssertionError

使用 assert() 语言结构进行断言时,可能抛出这个错误:

ini_set('zend.assertions', 1); // 执行代码
ini_set('assert.exception', 1); // 允许抛异常

$test = 1;
assert($test === 0);

ParseError

  • 通过 included 或 required 引入文件有语法错误
  • eval() 解析的字符串有语法错误
try {
    eval('ech 66');
    include 'has-error.php';
} catch (ParseError $e) {
    echo $e->getMessage(), "\n";
}
syntax error, unexpected '66' (T_LNUMBER)

TypeError

函数的参数或返回值跟类型不匹配时,抛 TypeError:

function add(int $left, int $right)
{
    return $left + $right;
}

try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}
Argument 1 passed to add() must be of the type integer, string given, called in D:\workspace\szhz\application\controllers\tuan\Index.php on line 312

Exception

Exception 出现的原因

PHP 在使用异常机制之前,通过返回错误码来表示函数的执行结果。部分函数返回 TRUE 或 FALSE,部分函数返回 0 或 1、-1。难以统一且无法包含足够的报错原因等信息。例如 strtotime() 函数,成功则返回时间戳,否则返回 FALSE,但是在 PHP 5.1.0 之前本函数在失败时返回 -1。

异常机制避免了错误码机制的一些不足,可以在 一次捕获多个异常。异常对象包含错误信息、错误码、错误行号、文件、上下文,更方便定位问题。

Exception 特点

Exception 是必须手动抛出并且可被捕获的。如果抛出的异常未被捕获,则导致 Fatal error,并使得代码停止执行。

function myDiv($a, $b) {
    if ($b == 0) 
        throw new Exception('Divided by zero');

    return $a/$b;
}

try {
    myDiv(1, 0); // 如果不捕获异常,则报错 Fatal error,并停止执行
} catch (Exception $e) {
    echo $e->getMessage(), "\n";
}

// 异常捕获后,可以继续执行后面的代码
...

自定义 Exception

自定义的 Exception 需要继承自已有异常,定义完成后就可以在代码中抛出自定义的这些异常。

<?php

class pdoDbException extends PDOException { 
    public function __construct(PDOException $e) { 
        if(strstr($e->getMessage(), 'SQLSTATE[')) {
            echo 'this is my exception';
        } 
    } 
}

function f() {
    try { 
        $pdo = new PDO('123.207.7.188', '$username', '$password', []); 
    } catch (PDOException $e) { 
        throw new pdoDbException($e); 
    }
}

try {
    f();
} catch (pdoDbException $e) {
    print_r($e);
}

用户自定义异常处理程序

set_exception_handler('myExceptionHandler');

function myExceptionHandler($exception) {
    echo $exception->getMessage();
}

function myDiv($a, $b) {
    if ($b == 0)
        throw new Exception('Divided by zero');

    return $a/$b;
}

myDiv(1, 0);

// 自定义异常处理程序执行后,不会继续执行后面的代码
<think>好的,我现在需要回答用户关于PHPExceptionError类定义的错误级别以及它们的区别的问题。首先,我得回忆一下PHP异常错误处理机制。 用户提到他们之前提供的引用材料,里面讲到PHP异常处理与Java不同,PHP异常错误分开处理。我需要确认这一点是否正确。PHP中,异常是通过Exception类及其子类来处理的,而错误则是通过错误处理函数(如set_error_handler)来捕获的。不过,PHP7之后引入了Throwable接口,Error类也实现了这个接口,所以现在ErrorException都是可抛出的。 接下来,用户想知道ExceptionError类的错误级别定义。错误级别通常指的是E_ERROR、E_WARNING这些常量,它们属于错误类型,而异常本身并没有错误级别,而是通过try/catch块来捕获。比如,当发生一个错误时,如果是可捕获的错误(比如E_ERRORPHP7中可能转换为Error异常),可以通过set_error_handler处理,者使用try/catch捕获Error实例。 然后,我需要区分ExceptionError的不同。Exception通常用于程序逻辑中的异常情况,比如无效的参数传递,而Error通常指更严重的运行时问题,比如内存不足者类型错误。例如,尝试调用一个不存在的函数会抛出Error,而业务逻辑中的失败可能抛出Exception。 另外,错误级别在PHP中由预定义常量表示,比如E_ERROR是致命错误,E_WARNING是警告,这些可以通过error_reporting函数设置。而Exception类有代码消息,但不会直接关联到这些错误级别。不过,当使用ErrorException类时,可以将错误转换为异常,这样错误级别会被包含在异常对象中。 我需要确保回答中提到的内容准确,比如PHP版本的影响,特别是PHP7前后的差异,因为PHP7引入了Error类。同时,要举例说明两者的使用场景,比如触发错误抛出异常的不同方式,以及如何捕获它们。 最后,用户可能需要了解如何处理这两种情况,所以应该提到set_exception_handlerset_error_handler的使用,以及如何通过try/catch结构来捕获ErrorException。同时,生成的相关问题需要围绕这些知识点展开,比如错误处理机制、PHP7的变化、实际应用中的处理方式等。 总结一下,回答的结构应该包括: 1. ExceptionError的基本定义 2. 错误级别的归属(错误有级别,异常没有) 3. PHP版本差异(特别是PHP7) 4. 处理方式的区别(try/catch vs 错误处理函数) 5. 实际示例代码说明 6. 区别对比表格 7. 相关问题的生成 需要确保使用正确的中文术语,并且LaTeX格式符合要求,比如行内公式用$...$,独立公式用$$...$$,但在这个问题中可能涉及较少数学公式,更多是代码概念。因此,重点放在代码块的正确格式概念解释的清晰性上。</think>### PHP异常与错误类别的区别解析 #### 一、错误级别定义机制 1. **错误(Error)的级别** PHP通过预定义常量定义错误级别,例如: - `E_ERROR`(致命错误,脚本终止) - `E_WARNING`(非致命警告,脚本继续) - `E_NOTICE`(提示性信息) 错误级别可通过`error_reporting()`函数动态设置,例如: ```php error_reporting(E_ALL & ~E_NOTICE); // 显示所有错误,但忽略NOTICE ``` 2. **异常Exception)的特性** 异常本身**不关联错误级别**,而是通过继承`Exception`类自定义子类实现分类。例如: ```php class CustomException extends Exception {} try { throw new CustomException("业务逻辑异常"); } catch (CustomException $e) { echo "捕获自定义异常: ".$e->getMessage(); } ``` #### 二、PHP7+的核心变化 PHP7引入`Throwable`接口统一异常与错误的处理,形成以下结构: ``` Throwable ├─ Error(系统级错误) │ ├─ TypeError(类型错误) │ ├─ ParseError(语法解析错误) │ └─ ... └─ Exception(程序逻辑异常) └─ 用户自定义异常 ``` #### 三、触发与捕获方式对比 | 类别 | 触发方式 | 捕获方式 | 典型场景 | |------------|------------------------|------------------------------|--------------------------| | **Error** | 系统自动触发 | `set_error_handler()``try/catch`(PHP7+) | 内存耗尽、类型不匹配 | | **Exception** | 开发者主动`throw` | `try/catch` | 数据校验失败、API调用异常| **代码示例:** ```php // 错误处理(传统方式) set_error_handler(function($errno, $errstr) { echo "捕获错误: [$errno] $errstr"; }); trigger_error("手动触发警告", E_USER_WARNING); // 输出:捕获错误: [512] 手动触发警告 // 异常处理 try { throw new Exception("主动抛出的异常"); } catch (Exception $e) { echo "捕获异常: ".$e->getMessage(); // 输出:捕获异常: 主动抛出的异常 } ``` #### 四、核心区别总结 1. **错误级别绑定** 错误关联`E_*`常量定义的级别,而异常通过类继承体系分类[^1]。 2. **处理机制差异** - 错误默认输出到日志屏幕,需显式设置处理函数 - 异常必须通过`try/catch`捕获,否则触发致命错误 3. **设计目的** - **Error**:系统级问题(如内存分配失败) - **Exception**:业务逻辑中的可控异常(如用户输入校验失败) #### 五、扩展应用:错误转异常 PHP可通过`ErrorException`将错误转换为异常: ```php set_error_handler(function($errno, $errstr) { throw new ErrorException($errstr, 0, $errno); }); try { strpos(); // 错误调用(缺少参数) } catch (ErrorException $e) { echo "捕获转换后的异常: ".$e->getMessage(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值