转载自:http://c.biancheng.net/view/7635.html

PHP错误类型

任何程序员在开发时都可能会或多或少的造成一些失误,或其他原因造成错误的发生。当然,如果用户不愿意或不遵循应用程序的约束,也可能会在使用时引起一些错误发生。

PHP 程序的错误发生一般分为三类,分别是语法错误、执行时错误和逻辑错误。下面分别介绍一下:

1、语法错误

语法错误是在编程中最容易碰到也是最容易解决的一种错误,例如:遗漏一个分号时就会显示错误信息。这种错误会停止程序的执行,并显示出错信息。我们可以根据错误信息改正程序,然后重新执行。

【示例】下面通过简单的代码演示常见语法错误以及相关的错误信息。

<?php    $a = 1;    $b = 2;    $c = $a + $b    echo ;?>

上面代码中第 4 行末尾省略了

;

,所以运行上面的代码会显示如下错误信息:

Parse error: syntax error, unexpected 'echo' (T_ECHO) in D:\WWW\index.php on line 5

从上面的示例和运行结果可以看出,语法错误会阻止程序继续向下执行。只有解决了这些错误后程序才可以顺利执行。

2、运行时错误

运行时错误也是就在程序执行时出现的错误。这种程序的语法没有错误,但是在执行的过程中,PHP 会发现程序有些不合理的地方,从而提示出警告信息,但程序会继续向下执行。

【示例】把 0 作为除数时,会造成程序运行错误并输出错误信息。

<?php    $a = 1;    $b = 0;    $c = $a / $b;    echo "$a / $b = $c";?>

运行结果如下:

Warning: Division by zero in D:\WWW\index.php on line 4 1 / 0 = INF

3、逻辑错误

逻辑错误是一种发生在程序员思想上的错误。在发生逻辑错误时并没有明显的错误信息,因为程序在执行中不会报出任何的错误信息,并且程序会正常执行,只是输出的结果并不是我们期望的而已。

【示例】下面通过简单的代码演示逻辑错误及其输出结果。

<?php    $a = 1;    $b = 2;    if($a = $b){        echo '$a = $b';    }else{        echo '$a != $b';    }?>

运行结果如下:

$a = $b

通过上面的示例可以看到,逻辑错误是不会有任何提示信息的。想要避免就只能靠程序员在编写程序的时候提高注意了。这还是很短的一段代码,如果代码量特别巨大,那么修改起来就很费时间了。

php错误级别

PHP 中定义了许多不同级别的错误,如使用了未定义的变量会报出一个 notice 级别的错误,实例化一个未定义的类则会报出 fatal error 级别的错误。

可在 php.ini 配置文件中定义错误级别,如

error_reporting=E_ALL|E_STRICT

会设置最严格的错误级别,在代码中也可使用

error_reporting(E_ALL)

等来定义错误级别。

PHP 中的错误类型如下表所示。

常量 说明
1 E_ERROR 致命的运行时错误,一般是不可恢复的情况,例如内存分配导致的问题,后果是导致脚本终止、不再继续运行。
2 E_WARNING 运行时警告(非致命错误),仅给出提示信息,但是脚本不会终止运行。
4 E_PARSE 编译时语法解析错误,仅由分析器产生。
8 E_NOTICE 运行时通知,表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。
16 E_CORE_ERROR 在 PHP 初始化启动过程中发生的致命错误,类似 E_ERROR,但是是由 PHP 引擎核心产生的。
32 E_CORE_WARNING PHP 初始化启动过程中发生的警告(非致命错误),类似 E_WARNING ,但是是由 PHP 引擎核心产生的。
64 E_COMPILE_ERROR 致命编译时错误,类似 E_ERROR,但是是由 Zend 脚本引擎产生的。
128 E_COMPILE_WARNING 编译时警告(非致命错误),类似 E_WARNING,但是是由 Zend 脚本引擎产生的。
256 E_USER_ERROR 用户产生的错误信息,类似 E_ERROR,但是是由用户自己在代码中使用 PHP 函数 trigger_error() 来产生的。
512 E_USER_WARNING 用户产生的警告信息,类似 E_WARNING,但是是由用户自己在代码中使用 PHP 函数 trigger_error() 来产生的。
1024 E_USER_NOTICE 用户产生的通知信息,类似 E_NOTICE,但是是由用户自己在代码中使用 PHP 函数 trigger_error() 来产生的。
1024 E_STRICT 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
2048 E_RECOVERABLE_ERROR 可被捕捉的致命错误,表示发生了一个可能非常危险的错误,但是还没有导致 PHP 引擎处于不稳定的状态。如果该错误没有被用户自定义句柄捕获,将成为一个 E_ERROR,从而使脚本终止运行。
8192 E_DEPRECATED 运行时通知,启用后将会对在未来版本中可能无法正常工作的代码给出警告。
16384 E_USER_DEPRECATED 用户产生的警告信息,类似 E_DEPRECATED,但是是由用户自己在代码中使用 PHP 函数 trigger_error() 来产生的。
30719 E_ALL E_STRICT 除外的所有错误和警告信息。

表格中的值(数值或者符号)用于建立一个二进制位掩码,制定要报告的错误信息。可以使用按位运算符来组合这些值或者屏蔽某些类型的错误。

注意,在 php.ini 之中,只有 '|'、'~'、'!'、'^' 和 '&' 会正确解析。

在正式环境中,可能会发生各种未知的错误,这时可以定义 error_reporting(0),这样就能屏蔽错误了,用户不会在页面看到错误信息,而当排查错误时,依然可到 PHP 的执行错误日志中寻找相关信息。

PHP error_log():错误日志的配置和使用方法

对于 PHP 开发者来说,一旦某个项目投入使用,应该立即将配置文件 php.ini 中的 display_errors 选项关闭,以免因为这些错误所透露的路径、数据库连接、数据表等信息而遭到黑客攻击。但是任何一个项目在投入使用后,都难免会有错误出现,那么如何记录一些对开发者有用的错误报告呢?

我们可以在单独的文本文件中将错误报告作为日志记录。错误日志的记录,可以帮助开发人员或者管理人员查看系统是否存在问题。如果需要将程序中的错误报告写入错误日志中,只要在 PHP 的配置文件中,将配置项 log_errors 开启即可。

错误报告默认会记录到 Web 服务器的日志文件里,例如记录到 Apache 服务器的错误日志文件 error.log 中。当然也可以将错误日志记录到指定的文件中或发送到系统的 syslog(系统日志)中,下面来分别介绍一下。

1、使用指定的文件记录错误报告日志

如果想使用自己指定的文件记录错误日志,一定要确保这个文件存放在文档根目录之外,以减少遭到攻击的可能。并且该文件一定要让 PHP 脚本具有写权限。假设在 Linux 操作系统中,将 /usr/local/ 目录下的 error.log 文件作为错误日志文件,并设置 Web 服务器进程用户具有写的权限。然后在 PHP 的配置文件中,将 error_log 指令的值设置为这个错误日志文件的绝对路径。

需要将 php.ini 中的配置指令做如下修改:

error_reporting  =  E_ALL                             // 将会向PHP报告发生的每个错误  
display_errors = Off                                     // 不显示满足上条 指令所定义规则的所有错误报告  
log_errors = On                                           // 决定日志语句记录的位置  
log_errors_max_len = 1024                         // 设置每个日志项的最大长度  
error_log = E:/php_log/php_error.log         // 指定产生的错误报告写入的日志文件位置 

PHP 的配置文件按上面的方式设置完成以后,并重新启动 Web 服务器。这样,在执行 PHP 的任何脚本文件时,产生的所有错误报告都不会在浏览器中显示,而会记录在自己指定的错误日志 E:/php_log/php_error.log 中。

此外,不仅可以记录满足 error_reporting 所定义规则的所有错误,而且还可以使用 PHP 中的 error_log() 函数把错误信息发送到 web 服务器的错误日志或者到一个文件里。

error_log() 函数的原型如下所示:

error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] ) : bool

参数说明如下:

  • $message:需要记录的错误信息;
  • $message_type:设置错误应该发送到何处。可能的信息类型有以下几个:
    • 0:(默认值)将 $message 发送到 PHP 的系统日志,使用操作系统的日志机制或者一个文件,取决于配置文件中 error_log 设置了什么;
    • 1:将 $message 发送到参数 $destination 设置的邮件地址。 第四个参数 $extra_headers 只有在这个类型里才会被用到;
    • 2:(已废弃)不再是一个选项;
    • 3:$message 被发送到位置为 $destination 的文件里。字符 $message 不会默认被当做新的一行;
    • 4:将 $message 直接发送到 SAPI 的日志处理程序中。
  • $destination:目标,也就是错误消息被发送到的目的地。它的含义描述于以上,由 $message_type 参数所决定;
  • $extra_headers:额外的头。当 $message_type 设置为 1 的时候使用。 该信息类型使用了 mail() 的同一个内置函数。

【示例】以登入 Mysql 数据库为例,当登入失败时记录错误信息。

<?php
$link = mysqli_connect("127.0.0.1", "my_user", "my_password", "my_db");  if (!$link) {
    error_log('Mysql 数据库连接失败!',0);
    exit();
}?>

运行上面的代码,会在 php.ini 配置文件中 error_log 一项所设置的目录中生成对应的错误日志文件,文件内容如下:

[08-May-2020 13:17:31 PRC] PHP Warning:  mysqli_connect(): (HY000/1045): Access denied for user 'my_user'@'localhost' (using password: YES) in D:\WWW\index.php on line 2
[08-May-2020 13:17:31 PRC] Mysql 数据库连接失败!

2、错误信息记录到操作系统的日志里

错误报告也可以被记录到操作系统日志里,但不同的操作系统之间的日志管理也是不同的。在 Linux 上错误语句将送往 syslog(基于UNIX的日志工具),而在 Windows 上错误将发送到事件日志里,可以通过事件查看器来查看。

如果希望将错误报告写到操作系统的日志里,将 php.ini 配置文件中 error_log 项的值设置为 syslog 即可。

具体需要在 php.ini 中修改的配置指令如下所示:

error_reporting  =  E_ALL                   // 报告所发生的每个错误  
display_errors = Off                           // 不显示满足上条指令所定义规则的所有错误报告  
log_errors = On                                 // 决定日志语句记录的位置 
log_errors_max_len = 1024               // 设置每个日志项的最大长度  
error_log = syslog                             // 指定产生的错误报告写入操作系统的日志里 

除了一般的错误输出之外,PHP 还允许向系统 syslog 中发送定制的消息。虽然通过前面介绍的 error_log() 函数,也可以向 syslog 中发送定制的消息,但在 PHP 中为这个特性提供了需要一起使用的 3 个专用函数,如下所示:

1) openlog()

打开一个当前系统中日志器的连接,为向系统插入日志消息做好准备。并将提供的第一个字符串参数插入到每个日志消息中,该函数还需要指定两个将在日志上下文使用的参数,可以参考官方文档使用。

2) syslog()

该函数向系统日志中发送一个定制消息。需要两个必选参数,第一个参数通过指定一个常量定制消息的优先级。例如 LOG_WARNING 表示一般的警告;LOG_EMERG 表示严重地可以预示着系统崩溃的问题,一些其他的表示严重程度的常量可以参考官方文档使用。第二个参数则是向系统日志中发送的定制消息,需要提供一个消息字符串,也可以是 PHP 引擎在运行时提供的错误字符串。

3) closelog()

该函数在向系统日志中发送完成定制消息以后调用,用来关闭由 openlog() 函数打开的日志连接。

【示例】使用上面介绍的四个函数向系统 syslog 中发送定制的消息。

<?php
openlog("C语言中文网", LOG_PID, LOG_USER);
syslog(LOG_WARNING, "向 syslog 中发送定时消息,发送时间:".date("Y/m/d H:i:s"));
closelog();
?>

以 Windows 系统为例,通过右击“我的电脑/此电脑”选择“管理”选项,然后到系统工具菜单中,选择“事件查看器”,再找到“Windows 日志”下的“应用程序”选项,就可以看到我们自己定制的警告消息了。

PHP set_error_handler():自定义错误处理函数

PHP中提供一个 set_error_handler() 函数,使用该函数可以指定另一个函数作为错误处理函数,其语法如下:

set_error_handler(callable $error_handler [, int $error_types = E_ALL | E_STRICT ])

参数说明

  1. $error_handler 是用户自定义的函数名称,除了可以传入函数名,还可以传入 NULL 重置处理程序到默认状态,还可以传入引用对象和对象方法名的数组。

用户自定义函数的格式如下:

error_handler(int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]])

其中:

  • 第一个参数 $errno 表示错误的级别,是一个 integer 类型;
  • 第二个参数 $errstr 表示错误的信息,是一个 string 类型;
  • 第三个参数 $errfile 是一个可选参数,表示发生错误的文件名,是一个 string 类型;
  • 第四个参数 $errline 也是一个可选参数,表示发生错误的行号,是一个 integer 类型;
  • 第五个参数 $errcontext 同样是一个可选参数(在 PHP7.2.0 之后被弃用),表示错误发生时活动符号表的 array。也就是说 $errcontext 会包含错误触发处作用域内所有变量的数组。用户的错误处理程序不应该修改错误上下文(context)。

注意:如果 set_error_handler() 函数返回 FALSE,标准错误处理程序将会继续调用。

  1. $error_types 参数就像配置文件 php.ini 中 error_reporting 能够控制错误的显示一样,此参数能够用于屏蔽$error_handler 的触发。如果没有该掩码,无论 $error_reporting 是如何设置的,$error_handler 都会在每个错误发生时被调用。

如果之前有定义过错误处理程序,则返回该程序名称;如果是内置的错误处理程序,则返回 NULL。如果指定了一个无效的回调函数,同样会返回 NULL。如果之前的错误处理程序是一个类的方法,此函数会返回一个带类和方法名的索引数组(indexed array)。

【示例】自定义一个错误处理函数,并使用它来处理程序中的错误。

<?php
function error_handler($errno, $errstr, $errfile, $errline ) {
    echo "error number:".$errno."<br/>";
    echo "error msg:".$errstr."<br/>";
    echo "error file:".$errfile."<br/>";
    echo "error line:".$errline."<br/>";
    die('something error');
}    
set_error_handler("error_handler");
strpos();
?>

运行结果如下:

error number:2
error msg:strpos() expects at least 2 parameters, 0 given
error file:D:\WWW\index.php
error line:10
something error

注意:使用这种方式进行错误处理,如果没有在错误处理函数中终止程序的执行,程序将会继续执行发生错误的下一行,所以需要使用 die() 函数来终止程序的运行。

另外需要注意的是,这种错误处理方式并不能接管所有级别的程序错误,E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING 以及 E_STRICT 部分的错误将会以最原始的形式显示出来。

set_error_handler() 进阶

PHP 的异常处理机制不完善,无法自动抛出异常,用户也可使用 set_error_handler() 这种方式将异常当作错误来处理,这样用户就可以使用自定义的错误处理函数来自动捕获异常了。

【示例】使用自定义的错误处理函数来自动捕获异常。

<?php
function error_handler($errno, $errstr, $errfile, $errline ) {
    echo "error number:".$errno."<br/>";
    echo "error msg:".$errstr."<br/>";
    echo "error file:".$errfile."<br/>";
    echo "error line:".$errline."<br/>";
    die('something error');
}
set_error_handler("error_handler");
/* 触发异常 */ 
try {
    $a = 5/0;
    echo $a;
} catch(Exception $e) {
    echo $e->getMessage();
}
?>

运行结果如下:

error number:2
error msg:Division by zero
error file:D:\WWW\index.php
error line:12
something error

当程序执行到第 12 行$a = 5/0;语句时,程序会自动捕获了这个异常,并由用户自定义的函数进行处理。

PHP屏蔽错误

在编写 PHP 代码的时候难免会出现错误,当很多错误信息堆积到一起的时候,可能会使我们感到反感,而且错误信息还会影响用户体验。最重要的是,如果这些错误被有心人士看到的话,可能对整个程序带来威胁,那么我们应该如何屏蔽 PHP 中的错误呢?

PHP 中屏蔽错误的方法有 3 种,下面就来分别介绍一下。

1、使用错误控制运算符:@

PHP 支持使用错误控制运算符@。将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误信息都将被忽略掉。

如果用 set_error_handler() 设定了自定义的错误处理函数,这个错误处理函数仍然会被调用,而如果在出错语句前使用了@的话,错误处理函数将返回 0。

需要注意的是,@运算符只对表达式有效。简单来说就是,如果能从某处得到值,就能在它前面加上 @ 运算符。例如可以在变量、函数、include 调用、常量等等之前使用 @ 运算符,但不能把它放在函数或类的定义之前,也不能用于条件结构例如 if 和 foreach 等语句前。

@运算符对于可以导致程序终止的严重错误也是有效的,这意味着如果在某个不存在或者敲错了字母的函数调用前用了@来抑制错误信息,那么程序将没有任何提示的死在那里。

【示例】使用 @ 错误控制运算符屏蔽代码中的错误。

<?php
$link = @mysqli_connect("127.0.0.1", "my_user", "my_password", "my_db") or die('数据库连接失败!');
?>

运行结果如下:

数据库连接失败!

2、使用 error_reporting() 函数屏蔽错误

PHP 有诸多错误级别,使用 error_reporting() 函数可以设置 PHP 会报告何种错误,函数的语法格式如下:

error_reporting([int $level])

其中参数 $level 为设置错误级别,具体可以使用的值可以在《PHP错误级别》一节中查看。如果将 $level 设置为 0,将关闭所有 PHP 错误报告;如果设置为 -1,将返回所有的错误报告。

【示例】使用 error_reporting() 函数屏蔽代码中的错误。

<?php
error_reporting(0);
$link = mysqli_connect("127.0.0.1", "my_user", "my_password", "my_db") or die('数据库连接失败!');
?>

3、通过 display_errors 参数屏蔽错误

这种方法应该是最彻底的一种解决办法,因为前两种方法只能作用于单行或者单个文件,而这个则是作用于所有的 PHP 文件。

具体的操作方法就是,打开 php.ini 配置文件,在其中搜索 display_errors,然后将 display_errors 的值设置为 Off 即可关闭所有的 PHP 错误报告。如下所示:

; This directive controls whether or not and where PHP will output errors,
; notices and warnings too. Error output is very useful during development, but
; it could be very dangerous in production environments. Depending on the code
; which is triggering the error, sensitive information could potentially leak
; out of your application such as database usernames and passwords or worse.
; For production environments, we recommend logging errors rather than
; sending them to STDOUT.
; Possible Values:
;   Off = Do not display any errors
;   stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
;   On or stdout = Display errors to STDOUT
; Default Value: On
; Development Value: On
; Production Value: Off
; http://php.net/display-errors
display_errors = Off

PHP7中的错误处理

在过去的 PHP中,处理致命错误几乎是不可能的。致命错误不会调用由 set_error_handler() 设置的处理方式,而是简单的停止脚本的运行。而在 PHP7 中改变了大多数错误的报告方式,不同于之前版本的错误报告机制,现在大多数错误被作为 Error 异常抛出。

这种 Error 异常可以像 Exception 异常一样被第一个匹配的 try catch 语句块所捕获。如果没有匹配的 catch 语句块,则调用异常处理函数(事先通过 set_exception_handler() 注册)进行处理。如果尚未注册异常处理函数,则报告为一个致命错误(Fatal Error)。

注意:Error 类并非继承自 Exception 类,所以不能用catch (Exception $e) { ... }来捕获 Error。可以用catch (Error $e) {...},或者通过注册异常处理函数 set_exception_handler() 来捕获 Error。

【示例】如下代码自动捕获一个致命错误。

<?php
try{
    $a = new cat();
}catch(Error $e) {
    echo 'Error 的信息:'.$e->getMessage().'<br>Error 发生的行号:'.$e->getLine();
}?>

运行结果如下:

Error 的信息:Class 'cat' not found
Error 发生的行号:3

在 PHP7 中实现了一个全局的所有可以通过 throw 语句抛出的异常的 throwable 基础接口,原有的 Exception 和新增的部分 Error 都实现了这个接口,以接口的方式定义了异常的继承结构,也明确了Exception 和各种 Error 都可以被当做一种可抛出的异常来处理。

要知道的是目前 PHP7 中并不是所有的错误均实现了 throwable 接口或继承至 PHP7 新增的 Error 类,原先 PHP5 中的一些错误特别是一些 E_PARSE、E_ERROR 级别的致命错误在 PHP7 中变为了可捕获的 throwable(Exception 对象或子对象和 Error 对象或子对象),如果不进行捕获则为一个错误,如果捕获就变为一个可在程序内处理的异常(throwable)。

基于上述分析可以这样理解:PHP7 中的异常所表示的范围相对于 PHP5 更为宽泛了,既包含了 PHP5 中的 Exception,又新增了 PHP7 才加入的 Error,而 Exception 和 Error 又都是实现了 throwable 接口,所以 PHP7 之后谈到异常首先应该想到的是 throwable 而不是 Exception。

throwable 接口的层次结构如下所示:

Throwable
  ├─ Error
  │    ├─ ArithmeticError
  │    │     └─ DivisionByZeroError
  │    ├─ AssertionError
  │    ├─ CompileError
  │    │    └─ ParseError
  │    └─ TypeError
  │           └─ ArgumentCountError
  └─ Exception
         └─ ...(各种 Exception 的子类)