更多优质内容
请关注公众号

PHP设计模式篇(五) 错误Error和异常Exception处理-张柏沛IT博客

正文内容

PHP设计模式篇(五) 错误Error和异常Exception处理

栏目:PHP 系列:PHP设计模式 发布时间:2022-03-16 15:08 浏览量:1597

- 错误和异常

PHP7中,错误是指 Error 类,异常是指 Exception 类,他们都继承自 Throwable 类


Exception和Error的不同点:

a. 产生方式不同和性质不同

Exception是异常,只能由用户手动throw new Exception才能产生一个异常;

Error是错误,是程序报错所产生,也可以由用户调用 trigger_error()产生。

由于Exception要由用户自己抛出,因此用户如果要通过Exception捕获错误,就需要在编程时自己判断可能会出现什么错误,并通过if判断加throw new Exception(...)的方式抛出错误,极其不便。


b. 捕获方式不同

Exception要通过 catch(Exception $e) 和 set_exception_handler 捕获;

Error要通过 catch(Error $e) 和 set_error_handler捕获,其中catch只能捕获Fatal Error,set_error_handler只能捕获Waning、Notice 和 Deprecated类型的错误。


在PHP7之后,set_exception_handler 不仅能捕获 Exception,也能捕获 Fatal Error 类型(但是不能捕获notice和warning)。因为PHP7之后,set_exception_handler的回调函数可以传入一个Throwable类的实例,而不仅限于Exception类的实例。


Exception和Error的相同点:

这两个类都继承自Throwable类,用 catch(Throwable $e) 可以同时捕获 Exception 和 Fatal Error类型的错误或异常。

Exception和Error具有相同的方法签名可以获得错误或异常信息:

getMessage();

getFile();

getLine()

特别注意:虽然我们自己写的代码很少会 throw Exception,但是类似PDO、Redis之类的PHP类库内部可能会throw new Exception,因此用catch(Exception $e)在这些情况下依旧能产生作用。



平时我们的错误主要包含以下几种(按严重级别排序):

Parse error > Fatal Error > Waning > Notice > Deprecated

以上所有错误级别中,所有错误级别都不属于 Exception。


小结:

catch(Exception $e) 只能捕获Exception,无法捕获Error;

catch(Error $e)只能捕获Fatal Error错误,不能捕获 Warning、Notice、Deprecated错误,也不能捕获Exception。

catch(Throwable $e)可以捕获Fatal Error错误 和 Exception异常。

set_error_handler() 可以捕获Warning、Notice、Deprecated,不能捕获 Fatal Error和Parse error。

set_exception_handler()可以捕获Exception和Error。


被 set_error_handler() 的自定义错误处理函数捕获到错误后,主逻辑仍可继续执行(因为Warning和Notice错误不足以终止程序)。

被 set_exception_handler() 的自定义错误处理函数捕获到错误或异常后,主逻辑不会继续往下执行。




- PHP错误 Error

PHP的错误捕获流程(不含Fatal Error):

a. 如果有注册自定义错误处理函数,则发生Warning、Notice、Deprecated,会被 set_error_handler注册的错误处理函数 myErrorHandler() 捕获。是否报错取决于 myErrorHandler()返回true还是false。

b. 如果没有注册自定义错误处理函数,则直接报错。


set_error_handler()介绍:

set_error_handler(callable $error_handler, int $error_types = E_ALL | E_STRICT): mixed

第一参 自定义的错误处理函数。

php要求自定义错误处理函数的参数要遵循一下协议规则:

handler(int $errno,	// 本次捕获到的错误的级别,是一串二进制标志位组成的错误级别
    string $errstr,	// 错误的信息
    string $errfile = ?,	// 发生错误的文件名
    int $errline = ?	//错误发生的行号
): bool

自定义的错误处理函数 return false会直接报错,return true不会报错,return null(或者不return)也不会报错。


第二参 $error_types 表示自定义错误处理函数只捕获哪些级别的错误,不在$error_types范围内的错误不会被$error_handler捕获。

即使在外部设置了error_reporting()不报告 warning/notice 这样的错误,$error_handler也能捕获到warning/notice错误。即 error_reporting()不会干扰到第二参$error_types。

E_STRICT是代码规范性的问题,不算错误。因此E_ALL不包含E_STRICT。


需要注意:如果错误发生在 set_error_handler()调用之前,那么这个错误是无法被捕获的。


下面是 set_error_handler()的官方示例:

set_error_handler("myErrorHandler", E_ALL);
function myErrorHandler($errno, $errMsg, $errFile, $errLine){
    if ($errno & error_reporting() == 0){
        return false;	// 如果要捕获的错误不是我指定的错误类型,则外界依旧报错,外界会继续往下执行(因为warning和notice级别的错误本身就不影响继续执行)
    }

    var_dump("MyError info:".$errMsg);
    return true;	// 返回true则外界不报错,会继续往下执行
}

// 例子1:调用不存在的函数。结果直接报错,没有被handler捕获,因为致命错误只会被catch捕获
a();	

// 例子2:引发一个warning错误。不会被catch捕获,但会被handler捕获。
try{
    1/0;
}catch(Error $e){
    var_dump($e);
}

// 例子3:致命错误被catch捕获,没有被handler捕获
try{
    a();
}catch(Error $e){
    var_dump($e);
}


注意:error_reporting()会返回handler外界设置的错误级别。例如外界设置了 error_reporting(E_NOTICE),set_error_handler("handler", E_ALL),那么在handler内部调用 error_reporting()会返回 E_NOTICE 而非 E_ALL。

如果在handler之外设置了 error_reporting(E_NOTICE),但是发生了 E_WARNING错误,这个错误依旧能被handler捕获到。如果handler内部要按照 外界的要求只处理 E_NOTICE 的错误,就可以在handler做个if ($errno & error_reporting() == 0){return false;} 的判断,表示handler不处理 E_WARNING,而是直接报错。




- PHP异常 Exception

异常和Fatal Error的捕获流程:

a. 优先被catch捕获,如果没有try catch 则会被 set_exception_handler 捕获。

b. 如果用 set_exception_handler 捕获异常成功,则外界后面的所有代码都不会执行;如果用 try catch捕获成功,则后面的主代码仍可以继续执行。

c. 如果抛出一个Exception而没有try catch 或 set_exception_handler 捕获,就会报一个 Fatal Error错误。


最后,语法错误是无法用 set_error_handler 和 set_exception_handler 捕获的。


使用set_exception_error捕获异常。

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);	// 自定义异常处理程序执行后,不会继续执行后面的代码123
// a();		// 发生 Fatal Error 同样会被 myExceptionHandler 捕获。

echo 123;



现在有一个需求:如何让一个try catch 能够捕获所有的 Exception、Fatal Error、Warning、Notice、Parse错误。

思路:在 set_error_handler的回调函数中,将捕获到的Warning和Notice作为Exception抛出。再用 set_exception_handler 捕获异常。这么一来所有错误都能被当做异常捕获。

<?php


class MyException extends Exception{}


// 捕获 warning、notice 错误
function myErrorHandler($errno, $errStr, $errFile, $errLine){
    if (error_reporting() & $errno == 0){
        return false;
    }
    $msg = "\"{$errStr}\" occured in File \"{$errFile}\" at Line {$errLine}\n";
    throw new MyException($msg, $errno);
}


function myExceptionHandler(Throwable $e){
    if($e instanceof MyException){
        echo $e->getMessage();
        return ;
    }
    echo "\"".$e->getMessage()."\" occured in File \"".$e->getFile()."\" at Line ".$e->getLine(). "\n";
}


function myShutdownFunc(){  // 如果某个错误已经被 catch 或者 error_handler 或者 exception_handler 捕获,就不会再被 error_get_last()记录
    $err = error_get_last();
    if($err && $err['type']){
        myErrorHandler($err["type"], $err["message"], $err['file'], $err['line']);
    }
}


set_error_handler("myErrorHandler", E_ALL);
set_exception_handler("myExceptionHandler");
register_shutdown_function("myShutdownFunc");    // 注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。

try{
    // 1/0;    // Warning 先被 myErrorHandler 捕获,再被下方catch捕获,打印123
    // a;      // Notice 先被 myErrorHandler 捕获,再被下方catch捕获,打印123
    // a();    // Fatal Error 被 myExceptionHandler 捕获,不打印123
}catch(Exception $e){
    var_dump($e);
}

echo 123;


无论程序是否把报错,都会在结束时执行 myShutdownFunc(),该函数的作用是做个错误兜底,如果有错误未被myExceptionHandler或者myErrorHandler或者catch捕获而在 myShutdownFunc 中被error_get_last()检测到,它无法阻止该错误报错,myShutdownFunc能做的就是将这个错误记录到日志中。



最后还有一个问题没解决:如何捕获Parse Error语法错误?就好像在Tp5和Laravel这样的框架里面,即使控制器层或者模型层有语法错误也不会引起真正报错,而是将错误记录到日志中。

实际上,php无法捕获语法错误,因为一个文件如果有语法错误,根本不会执行这个脚本里的任何代码就直接报错。

但可以将 错误注册逻辑 和 业务逻辑 分开放到不同文件,通过 include/require 引入业务逻辑的php文件,如果 include("test.php") 的 test.php 有语法错误,则 include就会返回一个Fatal Error错误,Fatal Error错误就会被 myExceptionHandler() 捕获。


如下所示:

// ... 注册错误和异常的handler
try{
    include("test.php");	// test.php有语法错误,include会引发致命错误,此时会被 myExceptionHandler() 捕获,不打印123。
}catch(Exception $e){
    var_dump($e);
}
echo 123;


如果写成catch(Throwable $e)就会被 catch捕获,会打印123。



目前主流的框架如Tp5和laravel等,它们的错误处理机制就是通过上面的方式实现:

set_error_handler();

set_exception_handler();

register_shutdown_function();

error_get_last();

try catch




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > PHP设计模式篇(五) 错误Error和异常Exception处理

热门推荐
推荐新闻