php的错误和异常

PHP中的异常的独特性,即PHP中的异常不同于主流语言C++、java中的异常。在Java中,异常是唯一的错误报告方式,而在PHP中却不是这样,而是把所有不正常的情况都视作了错误进行处理。这两种语言对异常和错误的界定存在分歧。什么是异常什么是错误,两种语言的设计者存在不同的观点。

说起PHP异常处理,大家首先会想到try-catch,那好,我们先看一段程序吧:

<?php
try {
    echo 1/0;
} catch (Exception $e){
    echo $e->getMessage();
}

我的问题是:这段程序能正确的捕捉到除0的错误信息吗?
如果你回答能,那你就把这篇文章看完吧!应该能学点东西。

其实,执行结果出现了一个警告级别的错误,程序终止。

Warning: Division by zero in E:\develop\test.php on line 5

  PHP通常是无法自动捕获有意义的异常,它把所有不正常的情况都视作了错误,你要想捕获异常就得使用if….else结构,保证代码是正常的,然后判断进行手动抛出异常。

异常与错误的概述

错误:是属于php脚本自身的问题,大部分情况是由错误的语法,服务器环境导致,使得编译器无法通过检查,甚至无法运行的情况。warning、notice都是错误,只是他们的级别不同而已,并且错误是不能被try-catch捕获的。
例如echo输出一个未赋值的变量,这类问题往往导致程序或逻辑无法继续下去而需要中断。

异常:是程序在运行中出现不符合预期的情况及与正常流程不同的状况。一种不正常的情况,按照正常逻辑本不该出的错误,但仍然会出现的错误,这是属于逻辑和业务流程的错误,而不是编译或者语法上的错误。
比如接收到一个长度超出预定格式的用户名。因此,异常主要靠编码人员做预先判断后抛出,捕获异常后改变程序流程来处理这些情况,不必中断程序。

PHP中的错误级别

PHP对于异常和错误的界定似乎不是很明显,尤其是低版本的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 编译时致命性错。这就像由Zend脚本引擎生成了一个E_ERROR。
128 E_COMPILE_WARNING 编译时警告(非致性错)。这就像由Zend脚本引擎生成了E_WARNING警告。
256 E_USER_ERROR 自定义错误消息。类似 E_ERROR, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
512 E_USER_WARNING 自定义警告消息。用PHP函数trigger_error(程序员设的E_WARNING警告)
1024 E_USER_NOTICE 自定义的提醒消息。像由使用PHP函数trigger_error(程序员E_NOTICE集)
2048 E_STRICT 编码标准化警告。允许PHP建议修改代码以确保最佳的互操作性向前兼容性。
4096 E_RECOVERABLE_ERROR 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被用户自定义句柄捕获 (参见 set_error_handler()),将成为一个E_ERROR 从而脚本会终止运行。
8192 E_ALL 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
16384 E_USER_DEPRECATED 用户产少的警告信息。 类似 E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
30719 E_ALL E_STRICT除外的所有错误和警告信息。

选择性设置显示错误

<?php
error_reporting(0);                //禁用错误报告
error_reporting(E_ERROR | E_WARNING | E_PARSE);//报告运行时错误
error_reporting(E_ALL);            //报告所有错误
error_reporting(E_ALL ^ E_NOTICE); //除E_NOTICE报告所有错误,是在php.ini的默认设置
error_reporting(-1);               //报告所有 PHP 错误
error_reporting(3);                //不报E_NOTICE
error_reporting(11);               //报告所有错误
ini_set('error_reporting', E_ALL); // 和 error_reporting(E_ALL); 一样
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);  //表示php错误,警告,语法错误,提醒都返错。

  PHP中的异常机制是不足的,绝大多数情况下无法自动抛出异常,必须使用if….else语句先进行判断,在进行手动抛出异常。
  如果你想在数据库连接失败的时候自动捕获异常是行不通的,因为这就不是异常,是错误。但是在java中就不一样了,他会把很多和预期不一致的行为当做异常来进行捕获。
  因此我们可以通过一些特殊的函数(register_shutdown_function,set_error_handler,set_exception_handler)来自定义错误处理函数,通过这几个函数我们可以实现PHP假自动捕获异常和错误。

PHP异常处理中的黑科技

set_error_handler()

看到这个名字估计就知道什么意思了,这个函数用于捕获错误,设置一个用户自定义的错误处理函数。

# 方式一:
set_error_handler('show_error');
function show_error($type, $message, $file, $line)
{
    var_dump('set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line');
}

# 方式二:
class ErrorClass{
    // 必须静态public方法
    public static function show_error($errorNum, $errorMs, $errorFile, $errorLine){
        echo('set_error_handler: ' . $errorNum . ':' . $errorMs . ' in ' . $errorFile . ' on ' . $errorLine . ' line ');
    }
}
set_error_handler(['ErrorClass', 'show_error']);

try {
    echo 1/0;
} catch (Exception $e){
    echo $e->getMessage();
}

输出结果:

set_error_handler: 2:Division by zero in E:\develop\\test.php on 22 line

由结果可知:我们自定义的show_error方法截取了错误,此时我们可以主动的处理这些错误,抛出相应的异常。

但是我们需要注意以下两点:

  • 如果存在该方法,相应的error_reporting()就不能在使用了。它将接管PHP原生错误处理函数,即所有的错误都会交给自定义的函数处理。
  • 此方法不能处理以下级别的错误:E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,set_error_handler() 函数所在文件中产生的E_STRICT,该函数只能捕获系统产生的一些Warning、Notice级别的错误。

register_shutdown_function()

  捕获PHP的错误:Fatal Error、Parse Error等,这个方法是PHP脚本执行结束前最后一个调用的函数,比如脚本错误、die()、exit、异常、正常结束都会调用。
  通过这个函数就可以在脚本结束前判断这次执行是否有错误产生,这时就要借助于一个函数:error_get_last();这个函数可以拿到本次执行产生的所有错误。error_get_last();返回的信息:

/*
 * [type]    - 错误类型
 * [message] - 错误消息
 * [file]    - 发生错误所在的文件
 * [line]    - 发生错误所在的行
*/
register_shutdown_function('shutdownfunc');
function shutdownfunc()
{
   if ($error = error_get_last()) {
       echo "<pre>";
       print_r($error);
       echo "</pre>";die;
   }
}

try {
    echo 1/0;
} catch (Exception $e){
    echo $e->getMessage();
}

输出结果:

Warning: Division by zero in E:\develop\test\test1.php on line 16
Array
(
    [type] => 2
    [message] => Division by zero
    [file] => E:\develop\test.php
    [line] => 16
)

注意事项:

  • register_shutdown_function()函数可重复调用,但执行的顺序与注册的顺序相同
  • register_shutdown_function()函数执行在headers发送之后
  • 如果在调用register_shutdown_function()函数之前有exit()函数调用,register_shutdown_function()函数将不能执行
function test() {
    echo "test()";
}
register_shutdown_function("test");
echo "show: ";die;
# 输出:show: test()

function test() {
    echo "test()";
}
echo "show: ";die;
register_shutdown_function("test");
# 输出:show:

set_exception_handler()

  设置默认的异常处理程序,用在没有用try/catch块来捕获的异常,也就是说不管你抛出的异常有没有人捕获,如果没有人捕获就会进入到该方法中,并且在回调函数调用后异常会中止。看一下用法:

<?php
# 第一种放方法
function myException($exception) {
    echo "Exception: " . $exception->getMessage();
}
set_exception_handler('myException');

# 第二种方法
class MyError{
    # 必须是静态public方法
    public static function myException($exception) {
        echo "Exception: " . $exception->getMessage();
    }
}
set_exception_handler(['MyError', 'myException']);
throw new Exception('Uncaught Exception occurred');

执行结果:

Exception: Uncaught Exception occurred

自定义异常处理

class myException extends Exception {
    public function myExceptionMessage(){
        return  'Error line ' . $this->getLine() . 
                ' in ' . $this->getFile() . 
                ': ' . $this->getMessage() . ' Must >= 0';
    }
}

$age = -1;
try {
    $age = intval($age);
    if($age < 0) {
        throw new myException($age);
    }

} catch (myException $e) {
    echo $e->myExceptionMessage();

}

输出:
Error line 16 in E:\develop\test.php: -1 Must >= 0


参考文献:
PHP的异常处理、错误的抛出及错误回调函数
再谈PHP错误与异常处理

1000

GS

北京 | php攻城狮

创作 35 粉丝 2

fighting