PHP7的异常与错误处理

PHP7 异常处理

PHP5,致命错误处理方法非常粗暴,直接停止脚本的执行。

PHP7 中当致命错误和可捕获的错误( E_ERROR 和 E_RECOVERABLE_ERROR )发生时会抛出异常,而不是直接停止脚本的运行。特殊情况下,比如内存溢出时也可能会停止脚本执行。

Throwable

PHP7 新增了一个全新的接口 Throwable,Exception 和 Error 都实现了 Throwable 接口

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 可以捕获 try/catch 块中 Exception 和 Error 对象(或是任何未来可能)的异常类型。

如果要捕获特定类型的异常最好使用对应的 ***Exception 处理,Throwable 更偏向于处理未知错误和系统级错误。

在 PHP7 中,要捕获所有的错误应该使用 Throwable 而不是 Exception。

try {
   // 逻辑代码
} catch (Throwable $t) {
   // 补救处理
}

Error 内部错误

PHP7 中 fatal 或 recoverable 级别的错误都能抛出一个 Error 实例。可以使用 try/catch 块来捕获。

$user = [];
try {
    $user->getName(); 
} catch (Error $e) {
    // Call to a member function getName() on arrayecho 
    $e->getMessage();
}

Error 类细分出以下子类:

  1. ArithmeticError:位移操作负数位或调用 intdiv() 时分子是 PHP_INT_MIN 且分母是 -1 (这个使用除法运算符的表达式:PHP_INT_MIN / -1,结果是浮点型)
  2. DivisionByZeroError:intdiv() 的分母是 0 或者取模操作 (%) 中分母是 0
  3. AssertionError:assert() 的条件不满足
  4. CompileError:编译错误
  5. ParseError:include/require 文件或 eval() 代码存在语法错误
  6. TypeError:函数参数或返回值不符合声明的类型
  7. ArgumentCountError:递给用户定义的函数或方法的参数太少时被抛出

PHP手册参考:https://www.php.net/manual/zh/class.error.php

Exception 异常

RuntimeException

RuntimeExcption是执行异常,指程序身本以外无法由开发者控制的状况。 例如呼叫数据库,但是数据库没有回应,或者呼叫远程 API ,可是 API 却没有传回正确的数值。 另外也包含档案系统或环境的问题,例如程序要抓取的的某个外部档案不存在,或者应该安装的外部函式库没有正常运作,这些因为都不是开发者可以控制的,我们就统称为 RuntimeException。

Web 开发最常见的状况就是数据库连接失败,或者SQL查询错误。 例如 PDO 所丢出来的 PDOException 就是既承自 RuntimeException。 所以我们要判断是否是数据库错误可以这样写:

try {
  // some database operation
  $db->fetch();
} catch (RuntimeException $e) {
  // Database error
} catch (Exception $e) {
  // Other error
}

LogicException

LogicException 则是反过来,属于程序本身的问题,应该要是开发者事前就解决的问题。 例如某个应该要被 Override 的 method 没有被 override,但 class 本身又因故无法设为 abstract 时,我们就在 method 中丢出 LogicException,要求开发者一定要处理这个问题。 另外,class未被引入,呼叫不存在的对象,或是你把数值除以零等等都属于此例外。

class Command
{
  public function execute()
  {
    if ($this->handler instanceof Closure) {
      return $this->handler();
    }
    throw new LogicExcpetion('Handler should be callable.');
  }
}

由此可知,一个正常运作的系统,应该可以允许 RuntimeException 的出现,但是 LogicException 是应该要完全见不到的。

更多的 Exceptions

Runtime 系列

UnexpectedValueException

当一个返回的结果不是预期的值或类型时,例如数据库正确返回数据过来,或者函式正确返回数据,但数据内容却不是我们想要的时候,则丢出此例外。

$response = HttpClient::get($url);
if (!$response->getHead()) {
  throw new UnexpectedValueException('Response not return anything.');
}

OutOfBoundsException

当我们从一个不确定长度的阵列或容器取值,但索引值不合法或不存在时,丢出此例外。 因为不确定长度的阵列意味着内容可能是由外部资源或数据库决定的,不属于开发者的控制范围,故为Runtime Error。

$items = Database::loadAll('users');
if (!isset($items[$i])) {
  throw new OutOfBoundsException('User ' . $i . ' not exists.');
}

OverflowException

当我们加入一个元素到满载的容器时,同样的此容器的实际容量不被开发者控制,只有尝试加进去后才会发现此错误,那么便丢出此例外。

 

RangeException

这是 DomainException 的 Runtime 版本,通常用在你要访问一组预定义好的数据集内没有的项目时。 例如不属于 weekdays:foo

 

另外当我们试图存取的资源不存在时,也可以丢出此例外。 例如某个 extension 或数据库 Driver 尚未安装时,因为外部 extension 与套件不一定是开发者可以控制的,可能是网管或主机商决定,因此也属于 Runtime error。

 

UnderflowException

当我们从一个空容器或空阵列移除元素时,就会丢出此例外。

 

Logic 系列

BadFunctionCallException

呼叫了一个未被定义,或不允许被使用的函式,或者函式缺乏必备的参数,还有当 或类似的函式回传否时,丢出此例外。 由于这不是用户决定的,而是开发者的问题,故属于 Logic Exception。

 

BadMethodCallException

与 BadFunctionCallException 相似,是 Method 用的版本。

有时可以用在 抓不到对应的执行对象时:__call()

 

InvalidArgumentException

不合规格的函式参数丢进来时,比如应该是字串,却拿到阵列时,丢出此例外。 与 TypeError 的差别在于不单纯只有类型不合,任何不允许的内容都可以丢出此例外。

DomainException

这是的 Logic 版本,用在访问一系列已定义的资料群集时,找不到对应的资源则丢出此例外。 例如没有限制允许处理的图片类型时:RangeException

又或者你尝试处理某张 tiff 图片,但 tiff 的解析引擎却还没实作在程序中,皆可属于 DomainException

也可以用在针对外部资源的内部实现是否存在的判断,例如有人想要操作 MangoDB 时,这个 Driver 的 class 却尚未在框架中的数据库存取器实作。

 

LengthException

当一个文件或参数的长度不符合或高于预期时,例如一个档案砸凑值长度跟原本预设的不一样,则丢出此例外。

 

OutOfRangeException

OutOfBoundsException 的 Logic 版本,当我们试图从一个固定长度数据集存取不合法的索引值时丢错。

例如:

 

与 RangeException 和 DomainException 的差别在于他们是针对值本身的存在与否,而 OutOfRangeException 与 OutOfBoundsException 是针对索引的存在与否。

 

参考以下文章:

https://trowski.com/2015/06/24/throwable-exceptions-and-errors-in-php7/

http://asika.windspeaker.co/post/3503-php-exceptions

发表评论