PHP中的错误和异常

821 阅读6分钟

引言

错误和异常为开发者提供了处理程序运行时错误的机制,对于程序设计来说正确的异常处理能够防止泄露程序自身细节给用户,给开发者提供完整的错误回溯堆栈,同时也能提高程序的健壮性

PHP中的错误与异常基础概念

PHP中什么是错误?

错误的语法,服务器环境导致等等

属于php脚本自身的问题,大部分情况是由错误的语法,服务器环境导致,使得编译器无法通过检查,甚至无法运行的情况。warning、notice都是错误,只是他们的级别不同而已,并且原生PHP的正常情况下,错误是不能被try-catch捕获的

PHP中什么是异常?

逻辑和业务流程的错误,而不是编译或者语法上的错误

程序在运行中出现不符合预期的情况,允许发生(你也不想让他出现不正常的情况)但他是一种不正常的情况,按照我们的正常逻辑本不该出现的情况,但仍然会出现的情况,属于逻辑和业务流程的问题,而不是编译或者语法上的错误

3、不同语言中的错误和异常是一样的么?

不一样,比如java中的异常指 和预期不一致

异常和错误的说法在不同的语言有不同的说法。在PHP中任何自身的错误或者是非正常的代码都会当做错误对待,并不会以异常的形式抛出,但是也有一些情况会当做异常和错误同时抛出(据说是,我没有找到合适的例子)。也就是说,你想在数据库连接失败的时候自动捕获异常是行不通的,因为这就不是异常,是错误。但是在java中就不一样了,他会把很多和预期不一致的行为当做异常来进行捕获

PHP通过哪几个函数可以实现PHP假自动捕获异常和错误?

  • register_shutdown_function:捕获PHP的错误:Fatal Error、Parse Error等,这个方法是PHP脚本执行结束前最后一个调用的函数,比如脚本错误、die()、exit、异常、正常结束都会调用
  • set_error_handler:捕获错误,设置一个用户自定义的错误处理函数
  • set_exception_handler:设置默认的异常处理程序,用在没有用try/catch块来捕获的异常,也就是说不管你抛出的异常有没有人捕获,如果没有人捕获就会进入到该方法中,并且在回调函数调用后异常会中止

PHP中的错误类型有哪些?

框架中的try-catch和php原生中的try-catch是否一样?

不一样,比如错误,原生的try-catch不能抛出,但是框架中的可以,因为框架中一般为我们扩展了try-catch功能(下边会大致介绍laravel底层的try-catch实现)

错误和异常捕获代码示例

把错误以异常的形势抛出

使用set_error_handler函数调用自定义异常处理函数,有两种使用方式

  • 直接传函数名 NonClassFunction:set_error_handler('function_name');
  • 传 class_name && function_name:set_error_handler(array('class_name', 'function_name'));
<?php
/**
 * 使用set_error_handler函数调用自定义异常处理函数
*/
set_error_handler('myselfErrorFunc');
function myselfErrorFunc($type$message$file$line)
{
    throw new \Exception($message ' 错误当做异常抛出'.PHP_EOL);
}

$num 0;
try {
    echo 1/$num;

} catch (Exception $e){
    echo $e->getMessage();
}

运行结果:

Division by zero 错误当做异常抛出

流程:本来是除0错误,然后触发set_error_handler(),在set_error_handler()中相当与杀了个回马枪,再把错误信息以异常的形式抛出来,这样就可以实现错误以异常的形式抛出。注意:这样做是有缺点的,会受到set_error_handler()函数捕获级别的限制

当程序出现错误的时候自动调用此方法,不过需要注意一下两点

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

原生php中try-catch捕获所有错误

由set_error_handler()可知,他能够捕获一部分错误,不能捕获系统级E_ERROR、E_PARSE等错误,但是这部分可以由register_shutdown_function()捕获。通过这个函数就可以在脚本结束前判断这次执行是否有错误产生,这时就要借助于一个函数:error_get_last();这个函数可以拿到本次执行产生的所有错误 exception_01.php

<?
<?php
register_shutdown_function('myShutDownFunc');
function myShutDownFunc()
{
    if ($error error_get_last()) {
        var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
    }
}
var_dump(23+-+); //此处语法错误

可以自己运行一下,看到根本就不会触发myShutDownFunc()函数,其实这是一个语法错误,直接报了一个

PHP Parse error:  syntax error, unexpected ')' in /Users/chenxingsheng/local_www/PHP/other/exception_03.php on line 9

由此引出一个奇葩的问题:问什么不能触发,为什么框架中是可以的?其实原因很简单,只在parse-time出错时是不会调用本函数的。只有在run-time出错的时候,才会调用本函数,我的理解是语法检查器前没有执行register_shutdown_function()去把需要注册的函数放到调用的堆栈中,所以就根本不会运行。那框架中为什么任何错误都能进入到register_shutdown_function()中呢,其实在框架中一般会有统一的入口index.php,然后每个类库文件都会通过include ** 的方式加载到index.php中,相当与所有的程序都会在index.php中聚集,同样,你写的具有语法错误的文件也会被引入到入口文件中,这样的话,调用框架,执行index.php,index.php本身并没有语法错误,也就不会产生parse-time错误,而是 include 文件出错了,是run-time的时候出错了,所以框架执行完之后就会触发register_shutdown_function();

所以按照下边这种分两个文件进行编写即可 exception_02.php

// 模拟语法错误
  var_dump(23+-+);

exception_03.php

<?php
register_shutdown_function('myShutDownFunc');
function myShutDownFunc()
{
    if ($error error_get_last()) {
        var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
    }
}

require 'exception_02.php';

运行结果

PHP Parse error:  syntax error, unexpected ')' in /Users/chenxingsheng/local_www/PHP/other/exception_02.php on line 2
string(146"<b>register_shutdown_function: Type:4 Msg: syntax error, unexpected ')' in /Users/chenxingsheng/local_www/PHP/other/exception_02.php on line 2</b>"

自定义异常处理类

自定义异常处理类必须是exception类的一个扩展,该类继承了PHP的exception类的所有属性,并且我们可以添加自定义的函数,使用的时候其实和之前的一样

<?php
class myselfException extends Exception
{
    public function errorMessage()
    {
        return 'Error line ' . $this->getLine().' in ' . $this->getFile()
            .': <b>' . $this->getMessage() . '</b> Must in (0 - 60)'.PHP_EOL;
    }
}

$age 70;
try {
    $age intval($age);
    if($age 60) {
        throw new myselfException($age);
    }

} catch (myselfException $e) {
    echo $e->errorMessage();
}

异常嵌套

异常嵌套是比较常见的写法,在自定义的异常处理中,try 块中可以定义多个异常捕获,然后分层传递异常,理解和冒泡差不多

<?php
$age 10;
try {
    $age intval($age);
    if($age 60) {
        throw new myselfException($age);
    }

    if ($age <= 0) {
        throw new Exception($age ' must > 0');
    }

} catch (myselfException $e) {
    echo $e->errorMessage();

} catch(Exception $e) {
    echo $e->getMessage();
}

也可以在catch中再抛出异常给上层

<?php
$age 100;
try {
    try {
        $age intval($age);
        if($age 60) {
            throw new Exception($age);
        }

    } catch (Exception $e) {
        throw new myException($age);

    }

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

PHP7中的异常处理

现在写PHP必须考虑版本情况,上面的写法在PHP7中大部分都能实现,但是也会有不同点,在PHP7更新中有一条:更多的Error变为可捕获的Exception,现在的PHP7实现了一个全局的throwable接口,原来老的Exception和其中一部分Error实现了这个接口(interface),PHP7中更多的Error变为可捕获的Exception返回给捕捉器,这样其实和前面提到的扩展try-catch影响范围一样,但是如果不捕获则还是按照Error对待

<?php
try {
    test();

} catch(Throwable $e) {
    echo 'Throwable:'.$e->getMessage() . ' 异常';
}

try {
    test();

} catch(Error $e) {
    echo 'Error:'.$e->getMessage() . ' 异常';
}

运行结果

Throwable:Call to undefined function test() zyfError:Call to undefined function test() 异常

因为PHP7实现了throwable接口,那么就可以使用第一个这种方式来捕获异常。又因为部分Error实现了接口,并且更多的Error变为可捕获的Exception,那么就可以使用第二种方式来捕获异常

官网提供的Throwable结构:

interface Throwable
  |- Error implements Throwable      
      |- ArithmeticError extends Error   
          |- ...
      |- AssertionError extends Error    
      |- ParseError extends Error        
      |- TypeError extends Error         
          |- ... 
  |- Exception implements Throwable
      |- ClosedGeneratorException extends Exception
      |- DOMException extends Exception
      |- ErrorException extends Exception
      |- IntlException extends Exception
      |- LogicException extends Exception
          |- ... 
      |- PharException extends Exception
      |- ReflectionException extends Exception
      |- RuntimeException extends Exception
          |- ... 

Error、Exception、Throwable之间的联系

以一个例子来说明 写了一段JSON解析的代码,由于数据源不能保证一定是 JSON,所以解析可能失败。但是PHP的json_decode遇到无法解析的字符串,是不报错的,会直接返回空。而即使能解析出来,我也不太敢相信里面的字段是始终一致的。所以,不但需要判断能否解析成JSON,还要判断字段是否缺少。出于偷懒的考虑,我想只捕获异常就好了,例如要捕获

Trying to get property of non-object

但是,下面的try catch怎么也捕获不到异常

try {
    // Code that may throw an Exception or Error.catch (\Exception $t) {
    // Handle exception
}

Google了一下,才知道,PHP中除了Exception还有 Error 的概念,而 Trying to get property of non-object,很不幸就属于Error。

PHP7开始,Error与Exception都是继承自 Throwable。从Throwable的继承关系,可以看到 Error与Exception是平级的关系

因此要想捕获

Trying to get property of non-object

可以使用

try {
    // Code that may throw an Exception or Error.catch (\Throwable $t) {
    // Handle exception
}

参考:https://www.cnblogs.com/Renyi-Fan/p/10739452.html