php 抛出异常使用场景

PHP 异常处理


异常用于在指定的错误发生时改变脚本的正常流程。


异常是什么

PHP 5 提供了一种新的面向对象的错误处理方法。

异常处理用于在指定的错误(异常)情况发生时改变脚本的正常流程。这种情况称为异常。

当异常被触发时,通常会发生:

  • 当前代码状态被保存
  • 代码执行被切换到预定义(自定义)的异常处理器函数
  • 根据情况,处理器也许会从保存的代码状态重新开始执行代码,终止脚本执行,或从代码中另外的位置继续执行脚本

我们将展示不同的错误处理方法:

  • 异常的基本使用
  • 创建自定义的异常处理器
  • 多个异常
  • 重新抛出异常
  • 设置顶层异常处理器

注释:异常应该仅仅在错误情况下使用,而不应该用于在一个指定的点跳转到代码的另一个位置。


异常的基本使用

当异常被抛出时,其后的代码不会继续执行,PHP 会尝试查找匹配的 "catch" 代码块。

如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 "Uncaught Exception" (未捕获异常)的错误消息。

异常的规则

  • 需要进行异常处理的代码应该放入 try 代码块内,以便捕获潜在的异常。
  • 每个 try 或 throw 代码块必须至少拥有一个对应的 catch 代码块。
  • 使用多个 catch 代码块可以捕获不同种类的异常。
  • 可以在 try 代码块内的 catch 代码块中抛出(再次抛出)异常。

简而言之:如果抛出了异常,就必须捕获它。

异常与错误的区别

首先要明白异常跟错误是两个不一样的概念,异常是出现正常逻辑之外的情况,而错误是指运行时出错了,比如,使用了一个未定义的变量等。异常需要抛出(throw)才能被捕捉到,而错误会导致程序执行终止。

PHP默认情况下,在代码出现了错误,如notice warning等消息时,错误信息会被直接打印到浏览器上,这个时候你通过 try catch是捕获不到错误信息的。php的try catch只能捕获到你自己 throw new Exception(“ “)抛出的错误,通过throw之后,程度会直接进入到catch中继续执行。如果你想抛弃php自身的错误处理机制,这个时候可以通过set_error_handler自定义一个函数用来处理,在这个函数中你可以抛出异常,然后再通过catch捕捉到异常。

异常介绍

PHP异常一般是指在业务逻辑上出现的不合预期、与正常流程不同的状况,不是语法错误。

PHP异常处理机制借鉴了java c++等,但是PHP的异常处理机制是不健全的。异常处理机制目的是将程序正常执行的代码与出现异常如何处理的代码分离。异常主要有检测(try)、抛出(throw)和捕获(catch)等操作。

PHP异常处理中需要注意的有,当代码中有throw出来的异常,则必须要catch到,也即是一个 try 至少要有一个与之对应的 catch。可以定义多个 catch 可以捕获不同的对象,php会按这些 catch 被定义的顺序执行,直到完成最后一个为止。而在这些 catch 内,又可以抛出新的异常。php的异常也像JAVA的异常的一样,可以在最外层catch捕捉,也可以在throw的地方捕捉。

当一个异常被抛出时,其后的代码将不会继续执行,PHP 会尝试查找匹配的 “catch” 代码块。如果一个异常没有被捕获,而且又没用使用set_exception_handler()作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出未能捕获异常(Uncaught Exception …)的提示信息。

PHP是无法自动捕获异常的(绝大多数),只有主动抛出异常并捕捉。也就是说,对于异常,是可预见的。目前PHP能自动抛出的异常不多,如:PDO类。

 

1. 什么时候才需要抛异常

这个一切从实际出发,如果你觉得你的代码可能会出现问题,就可以进行抛出异常

2.异常的类别

PHP7异常做了很多变动,异常类 Exception 和错误类 Error 都实现了 Throwable 接口

结构如下:

  • Throwable

    • Error

      • ArithmeticError

        • DivisionByZeroError
      • AssertionError
      • ParseError
      • TypeError

        • ArgumentCountError
    • Exception

      • ClosedGeneratorException
      • DOMException
      • ErrorException
      • IntlException
      • LogicException

        • BadFunctionCallException

          • BadMethodCallException
        • DomainException
        • InvalidArgumentException
        • LengthException
        • OutOfRangeException
      • PharException
      • ReflectionException
      • RuntimeException

        • OutOfBoundsException
        • OverflowException
        • PDOException
        • RangeException
        • UnderflowException
        • UnexpectedValueException
      • SodiumException

如何捕获异常

PHP中使用 try...catch...finally 捕获异常


public function test()
{
    try {
        //可能出错的代码逻辑
    } catch (\Exception $e) {
        echo $e->getMessage();
    } finally {
        //todo
    }
}

如果不确定出现异常还是错误,可以直接捕获 Throwable 异常

public function test()
{
    try {
        //可能出错的代码逻辑
    } catch (\Throwable $e) {
        echo $e->getMessage();
    } finally {
        //todo
    }
}

3. PHP 7 错误异常级别

在过去的 PHP 中,几乎不可能会去处理致命错误。致命错误只会轻易的终止脚本执行,而不会调用 set_error_hander() 错误处理程序。

在 PHP 7 中,当致命或是可恢复性错误 (E_ERROR and E_RECOVERABLE_ERROR) 发生时,异常会被捕获,而不是中止脚本。在特定的情况下,还是存在会致命的错误,比如内存不足之类,也会像之前一样立即中止脚本。PHP 7 中未捕获的异常依旧时致命错误。这意味着,如果 PHP 5.x 中未捕获的异常,在 PHP 7 中依旧是致命错误。

注意,例如警告或是通知错误在 PHP 7 中保持不变,只有致命错误或是可恢复性错误会抛出异常。

致命或是可恢复性错误的抛出并不延伸自 Exception 类。这种分离是为了防止现存的 PHP 5.x 代码接收到的错误异常调用到终止程序。致命或是可恢复错误抛出的异常将实例化一个新的异常类:Error。和其他异常类相同,被捕获到的 Error 类将会在最后一个程序块执行完毕之后再行处理。

相较于 PHP 7 alpha-2 之前,PHP 7 的异常类层次有所不同, 被抛出的致命和可恢复性的错误将于 EngineException 类实例化,而 EnginException 类并不继承于 Exception。Exception 和 EngineException 都继承于 BaseException。

Throwable

为了联合这两个异常分支,Exception 和 Error 都实现了一个新的接口,Throwable。

PHP 7 中新的异常层次如下:

Throwable //(接口)
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error 
        |- ParseError extends Error // 编译时错误
        |- ArithmeticError extends Error
            |- DivisionByZeroError extends ArithmeticError
        |- AssertionError extends Error
 

如果在 PHP 7 中定义 Throwable 接口,那应该和下面的代码相近。

interface Throwable

{

    public function getMessage(): string;

    public function getCode(): int;

    public function getFile(): string;

    public function getLine(): int;

    public function getTrace(): array;

    public function getTraceAsString(): string;

    public function getPrevious(): Throwable;

    public function __toString(): string;

}

这个接口应当很熟悉。Throwable 特定的方法和 Exception 的相同。唯一不同的是 Throwable::getPrevious() 会返回 Throwable Exception 和 Error 类的构造函数都将接收一个 Throwable 的实例作为先前的异常。

Throwable 可以在 try/catch 块中用老捕获异常或是错误对象(将来可能可以捕获更多的异常类型)。记住,这里更建议捕获更为具体的异常类,并采取相应的处理措施。然而,在一些场合下,需要宽泛的捕获异常(比如日志或是框架的错误处理)。在 PHP 7 中,这些异常捕获块更适合使用 Throwable 而不是 Exception。

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

自定义类不能实现 Throwable 插件,这个部分由于可预见性和一致性:只有实例化 Excetion 和 Error 类才能抛出异常。此外,异常携带了栈中被创建的对象的信息。自定义类并未自动拥有保存信息的参数。

Throwable can be extended to create package-specific interfaces or add additional methods. 只有继承了 Exception 或是 Error 的类才可以实现拓展了 Throwable 的插件。

interface MyPackageThrowable extends Throwable {}
 
class MyPackageException extends Exception implements MyPackageThrowable {}
 
throw new MyPackageException();

Error

在 PHP 5.下版本中所有的 errors 都是致命错误或是可恢复性致命错误,而在 PHP 7 中都抛出 Error 的实例化。像其他异常 Error 对象可以通过 try/catch 程序块捕获。

$var = 1;
 
try {
    $var->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
    // Handle error
}
 

通常,之前的致命错误都将抛出 Error 基类的实例化,但是一些错误会抛出更加确切的 Error 子类:TypeError, ParseError, and AssertionError。

TypeError (类型错误)

TypeError 实例化的抛出是由实参和形参 当调用函数时申明的形参和实参类型不一致(传入参数和方法中定义的参数类型不一致)将会抛出一个 TypeError 实例。

function add(int $left, int $right)
{
    return $left + $right;
}
 
try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}

得到的输出:

Argument 1 passed to add() must be of the type integer, string given

ParseError (解析错误)

included/required 文件,或者 eval() 中的代码包含语法错误时,ParseError 将会被抛出。

try {
    require 'file-with-parse-error.php';
} catch (ParseError $e) {
    echo $e->getMessage(), "\n";
}
 

ArithmeticError (算数错误)

抛出 ArithmeticError 错误有两种情况:负数位移,或者使用 PHP_INT_MIN 当作分子,-1 做分母调用 intdiv()(PHP_INI_MIN / -1 返回值是浮点型)。

try {
    $value = 1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage(), "\n";
}
 

DivisionByZeroError (分母为零)

分母为零时使用 intdiv() 或者取余(%) 会抛出 DivisionByZeroError 错误。注意,除零只会引起一个警告,计算结果为 NaN。

try {
    $value = 1 % 0;
} catch (DivisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}

AssertionError (断言)

当不满足 assert() 设定的条件时,将会抛出一个 AssertionError 错误。

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
 
$test = 1;
 
assert($test === 0);
 

不满足 assert() 设定的条件,抛出一个 AssertionError 错误,并且 assert.exception = 1,异常输出如下:

Fatal error: Uncaught AssertionError: assert($test === 0)

assert() is only executed and will only throw an AssertionError if assertions are enabled and set to throw exceptions with ini settings zend.assertions = 1 and assert.exception = 1.

使用 Error

用户可以创建自己的 Error 类,作为 Error 基类的拓展。这可能带来重要的问题:什么场合下应该抛出一个 Exception 类的子类实例,什么场合下又应该抛出 Error 类的子类实例?

由于错误对象不应当在程序运行中处理,捕获错误对象应当是少见的。通常而言,错误对象应当捕获并记录之,执行必要的清理,并给用户展示错误信息。

编写兼容 PHP 5.x 和 7 Exceptions 类的代码

在 PHP 5.x 和 7 使用相同的代码捕获异常,可以实用多重捕获代码块,首先捕获 Throwable,之后时 Exception。一旦不需要维护 PHP 5.x 的系统,代码块可以立刻被清理掉。

try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
    // Executed only in PHP 5.x, will not be reached in PHP 7
}

3.PHP如何进行错误与异常处理(PHP7中的异常处理和之前版本异常处理的区别)

一句话总结:

throwable接口+Error类

在PHP7更新中有一条:更多的Error变为可捕获的Exception,现在的PHP7实现了一个全局的throwable接口,原来老的Exception和其中一部分Error实现了这个接口(interface),PHP7中更多的Error变为可捕获的Exception返回给捕捉器,这样其实和前面提到的扩展try-catch影响范围一样,但是如果不捕获则还是按照Error对待

1、PHP7中更多的Error变为可捕获的Exception的代码表现是什么?

从catch(Exception 𝑒)到𝑐𝑎𝑡𝑐ℎ(𝑇ℎ𝑟𝑜𝑤𝑎𝑏𝑙𝑒e)到catch(Throwablee)和catch(Error $e)

<?php
    //php7写法
    try {
        test();

    } catch(Throwable $e) {
        echo $e->getMessage() . ' zyf';
    }


    //传统写法
    try {
        test();

    } catch(Error $e) {
        echo $e->getMessage() . ' zyf';
    }
?>

 

2、PHP中什么是异常?

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

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

 

3、PHP中什么是错误?

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

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

 

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

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

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

 

5、php中数据库连接失败是错误还是异常?

是错误,在php中是错误

数据库连接失败的时候自动捕获异常是行不通的,因为这就不是异常,是错误

 

6、java中的异常指什么?

和预期不一致

java会把很多和预期不一致的行为当做异常来进行捕获。

 

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

register_shutdown_function:捕获PHP的错误:Fatal Error、Parse Error等,这个方法是PHP脚本执行结束前最后一个调用的函数,比如脚本错误、die()、exit、异常、正常结束都会调用

set_error_handler:捕获错误,设置一个用户自定义的错误处理函数

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

 

8、php中的错误类型有哪几种?

Fatal Error:致命错误(脚本终止运行)

Parse Error:编译时解析错误,语法错误(脚本终止运行)

Warning Error:警告错误(仅给出提示信息,脚本不终止运行)

Notice Error:通知错误(仅给出通知信息,脚本不终止运行)

 

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

不一样,比如错误,原生的try-catch不能抛出,但是框架中的可以,框架中为我们扩展了try-catch功能

 

10、把错误以异常的形式抛出实例?

使用set_error_handler函数调用自定义异常处理函数

<?php
    set_error_handler('zyferror');
    function zyferror($type, $message, $file, $line)
    {
        throw new \Exception($message . 'zyf错误当做异常');
    }

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

    } catch (Exception $e){
        echo $e->getMessage();
    }
?>
好了,试一下,会打印出:
Division by zero zyf123

流程:本来是除0错误,然后触发set_error_handler(),在set_error_handler()中相当与杀了个回马枪,再把错误信息以异常的形式抛出来,这样就可以实现错误以异常的形式抛出。

大家要注意:这样做是有缺点的,会受到set_error_handler()函数捕获级别的限制。

 

11、原生php中try-catch如何捕获所有错误?

set_error_handler()和register_shutdown_function()

由set_error_handler()可知,他能够捕获一部分错误,不能捕获系统级E_ERROR、E_PARSE等错误,但是这部分可以由register_shutdown_function()捕获。

a.php内容:
<?
    // 模拟Fatal error错误
    //test();

    // 模拟用户产生ERROR错误
    //trigger_error('zyf-error', E_USER_ERROR);

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

    // 模拟Notice错误
    //echo $f;

    // 模拟Warning错误
    //echo '123';
    //ob_flush();
    //flush();
    //header("Content-type:text/html;charset=gb2312");
?>
b.php内容:
<?
    error_reporting(0);
    register_shutdown_function('zyfshutdownfunc');
    function zyfshutdownfunc()
    {
        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>');
        }
    }

    set_error_handler('zyferror');
    function zyferror($type, $message, $file, $line)
    {
        var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />');
    }

    require 'a.php';
?>

 

12、如何自定义异常处理类?

继承原先的 exception 类

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

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

    $age = 10;
    try {
        $age = intval($age);
        if($age > 60) {
            throw new zyfException($age);
        }

    } catch (zyfException $e) {
        echo $e->errorzyfMessage();

    }
?>

 

13、php中异常如何嵌套?

分层传递:try 块中可以定义多个异常捕获,然后分层传递异常,理解和冒泡差不多

抛给上层:catch中再抛出异常给上层

分层传递:try 块中可以定义多个异常捕获,然后分层传递异常,理解和冒泡差不多
<?php
    $age = 10;
    try {
        $age = intval($age);
        if($age > 60) {
            throw new zyfException($age);
        }

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

    } catch (zyfException $e) {
        echo $e->errorzyfMessage();

    } 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 zyfException($age);

        }

    } catch (zyfException $e) {
        echo $e->errorzyfMessage();
    }
?>

 

posted @ 2021-01-28 16:09  码农编程进阶笔记  阅读(325)  评论(0编辑  收藏  举报
返回顶部 有事您Q我