本文由 简悦 SimpRead 转码, 原文地址 xie.infoq.cn

手册异常处理

https://www.runoob.com/php/php-exception.html

  1. $b = 9;
  2. // 在 try 块 触发异常
  3. try
  4. {
  5. $a = 123;
  6. //主动抛出错误
  7. throw new Exception("try throw 抛出一个错误");
  8. // 如果抛出异常,以下文本不会输出
  9. echo '如果抛出异常,以下文本不会输出';
  10. $b = 456;
  11. }catch(Exception $e) // 捕获异常
  12. {
  13. echo $a; //123 可以输出try块中,抛出异常之前的变量
  14. echo 'Message: ' .$e->getMessage();
  15. echo $b; //9 try 异常之后的变量获取不到。所以获取的是刚开始申明的变量
  16. }
  17. 123 Message: try throw 抛出一个错误9

引言

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

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

PHP 中什么是错误?

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

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

PHP 中什么是异常?

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

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

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

不一样,比如 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 中的错误类型有哪些?

PHP 中的错误和异常 - 图1

框架中的 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’));
  1. <?php
  2. /**
  3. * 使用set_error_handler函数调用自定义异常处理函数
  4. */
  5. set_error_handler('myselfErrorFunc');
  6. function myselfErrorFunc($type, $message, $file, $line)
  7. {
  8. throw new \Exception($message . ' 错误当做异常抛出'.PHP_EOL);
  9. }
  10. $num = 0;
  11. try {
  12. echo 1/$num;
  13. } catch (Exception $e){
  14. echo $e->getMessage();
  15. }
  16. //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