PHP异常
预先定义的异常和SPL异常
PHP有两种类型的异常:PHP核心自身定义的异常和标准PHP库(SPL)中捆绑的SPL异常。由于SPL是默认内置的,因此可以作为PHP的标准功能使用,但手册中也有单独的页面介绍。
-
- 定義済み例外
- SPL例外
SPL例外
SPL的作者是Marcus Börger先生,他曾经担任过许多包括PDO在内的领导角色。(请参考PECL网站 https://pecl.php.net/user/helly)Marcus先生为SPL创建了一个名为Standard PHP Library的幻灯片,并从中选取了两个关于异常的片段作为样例。
「必须遵守的三个规则」
-
- 例外只在特殊情况下使用 (Exceptions are only used in special cases)
-
- 不要为控制流程使用例外 (Do not use exceptions for control flow)
- 不要将参数传递时使用例外 (Do not use exceptions for parameter passing)
有三条规则,但我认为它的含义是“例外只在真正例外的情况下使用,而不应该用于常规控制。”
例外是指像以下这样的情况。(摘自《PHP中的异常概述》)
-
- データベースが接続できない
-
- Web APIをアクセスできなかった
-
- ファイルシステムのエラー
- テンプレートファイルや設定ファイルのパースエラー
“创造特别的例外”
-
- 例外は”特別化(specialization)すべき = 独自のものを作るべき
- 組み込みの例外を拡張する (PHPマニュアル: 例外を拡張する)
class YourException extends Exception
{
}
建议您创建一个继承自异常类的FileNotExistException,而不是直接使用如下所示的RuntimeException。
throw new \RuntimeException("$fileがありません"); // OK
class FileNotExistsException extends \RuntimeException
{
}
throw new FileNotExistsException($file); // Better
LogicException与RuntimeException的区别
在PHP手册中,分别描述了以下内容:“表示程序逻辑内的错误的异常。如果发生这种类型的异常,应该修改自己编写的代码”,“当发生仅在运行时发生的错误时抛出”。然而,正如许多人所指出的,这些描述并不容易理解。Marcus先生的解释更加明确,并附有实际示例。
LogicExceptionはコンパイル時またはアプリケーション設計で検出ができる例外
RuntimeExceptionは実行時のみ検出できる例外。全てのデータベース例外のベースクラス
在检查代码时可以发现的异常(即bug)被称为LogicException,而只有在执行程序时才能发现的异常被称为RuntimeException。也就是说,可以将RuntimeException视为程序所依赖的外部条件或输入导致的异常。
逻辑异常的例子
<?php
abstract class MyIteratorWrapper implements Iterator
{
private $it;
public function __construct(Iterator $it)
{
$this->it = $it;
}
public function __call($func, $args)
{
$callee = [$this->it, $func];
if (!is_callable($callee)) {
throw new BadMethodCallException(); // LogicException
}
return call_user_func_array($callee, $args);
}
}
调用了不存在的方法是应用程序设计的问题。通过在执行之前检查程序,可以检测到这个问题。
运行时异常的例子
$fo = new SplFileObject($file); // RuntimeException
文件可能无法访问,也可能不存在。但程序本身没有错误。如果无法打开文件,SplFileObject将会引发RuntimeException异常。
$fo = new SplFileObject($file);
$fo->setFlags(SplFileObject::DROP_NEWLINE);
$data = array();
foreach($fo as $l) {
if (/*** CHECK DATA ***/) {
throw new Exception((); // RuntimeException
}
$data[] = $l;
}
由于使用了外部文件,每次读取数据时都会发生变化。这是运行时异常。
!preg_match($regex, $l) // UnexpectValueException
count($l=split(',', $l)) !=3 // RangeException
count($data) < 10) // UnderflowException
count($data) > 99) // OverflowException
count($data) < 10 || count($data) > 99) // OutOfBoundsException
所有这些也都是RuntimeException。
一个混杂的例子
$fo = new SplFileObject($file); // RuntimeException
$fo->setFlags(SplFileObject::DROP_NEWLINE);
$data = array();
foreach($fo as $l) {
if (!preg_match('/\d,\d/', $l)) {
throw new UnexpectedValueException(); // RuntimeException
}
$data[] = $l;
}
if (count($data) < 10) throw new UnderflowException(); // RuntimeException
// maybe more precessing code
foreach($data as $v) {
if (count($v) !== 2) {
throw new DomainException(); // LogicException
}
$v = $v[0] * $v[1];
}
进行乘法运算时,需要满足”必须有两个数字”的前提条件,否则会抛出数据域的异常。
注:有人指出 DomainException 的说明不够清晰(#47097),因此有了添加说明的经过(#291058)。在用户手册的用户注意事项中,有关”0 不是除数”、”“曜日”中不存在 foo”等处理数据不属于其处理范围(即数据域)的异常已经进行了解释说明。
LogicException在生产网站中不应该抛出的异常,RuntimeException可以说是在生产网站中可能会抛出的异常。
超出范围异常和越界异常的区别
这两个例外在名字和作用方面都很相似。
超出範圍異常 – 當要求無效的索引時抛出的異常,必須在編譯時檢測到。(邏輯異常)
超出範圍異常 – 當值不是有效的鍵時抛出的異常,這是一個在編譯時無法檢測到的錯誤。(運行時異常)
当考虑到这些区别的时候,我们也可以像考虑LogicException/RuntimeException的使用方式一样来思考。
在仅使用代码确定范围的情况下,违反该范围的情况被称为OutOfRangeException。在运行时,如果数据被外部读取,从而使用了无效的键,或者在运行时发现了超出范围的情况,那么就称为OutOfBoundsException。虽然OutOfRangeException是一个错误,但OutOfBoundsException是根据执行条件抛出的异常。
SPL例外使用的利弊
当考虑到SPL例外的不足之处时,正如SPL改进中所指出的那样,文档可能不够充足,也许不能作为开发者的共同语言。
在许多库和框架中,使用SPL异常的情况很常见,而且有许多博客文章也推荐这种用法。然而,在这个问题上也有一些相反的意见,虽然他们是少数派。我在StackOverflow上看到过@go_oh用户发表了类似的看法。
但是老实说,SPL的异常层级结构真的很混乱。所以,我觉得你可以自己创建你喜欢的异常(扩展Exception类)。
PHP运行时或逻辑异常的问题在stackoverflow上的解答:http://stackoverflow.com/questions/14171714/php-runtime-or-logic-exception
我个人长期以来一直使用SPL异常,但同时我也认同Gordon先生的观点。实际上,AuraPHP也未使用SPL异常,而是从PHP内置的\Exception类继承了所有自定义异常,这种方法非常干净且功能完善。
总结
-
- PHPにはSPLとして階層構造を持った例外機構が用意されていますが、他のSPL同様にSPL例外の利用はオプションです。また例外の考え方は言語や使用者にとって多様で、多くのディスカッションや意見があります。
-
- Exceptions are exceptionsという考え方は大切です。例外をgotoの代わりにプログラムの実行位置を変えるために使ったり、swtichやifの代わりに使うのはcontrol flowのために例外を使っているアンチパターンです。値を渡すための使うのも同様。
- 「書き込み権限がありません」とメッセージで例外を文で説明しないで、特別なエラークラス(NotWritableException)で区別するようにします(ドメイン固有の例外をそれぞれ用意すれば、Exceptionsフォルダを見ればそのプロジェクトではどのような例外が起こり得るのかが分かるようになります。)
参考资料
-
- Standard PHP Libraryスライド
-
- Exceptional PHP: Introduction to Exceptions
-
- SPL Improvements: Exceptions
-
- An Overview of Exceptions in PHP
-
- 例外 Advent Calendar 2014
- OutOfRangeException vs. OutOfBoundsException