(2015/07/02追記:PHP 7.0.0alpha2に合わせて修正しました)
PHPのエラーは、エラー文言を表示するだけの警告・注意と、その場で処理を終了してしまう致命的エラー(fatal error)の2種類に大別できます。
ところで、PHP5の致命的エラーには不便な点があります。それは、set_error_handler()
やその他の方法でエラーハンドリングできず、必ず終了してしまう点です。これでは致命的エラーをユニットテストするのも不便ですし、ReactPHPのようにサーバ動作させるようなプログラムも安心して使えません。
PHP7では致命的エラーが例外として実現されるようになり、エラーハンドリングの自由度が格段に上がりました。この変更の概要を紹介します。
例外のクラス階層の変更
まず、PHP7で例外クラスの階層がどう変わるかを説明します。PHP5までの例外のクラス階層は次のようなものでした。
- Exception
- ErrorException
- その他SPL例外
一方、PHP7からは次のようにThrowable
インターフェースが実装され、Exception
とError
がThrowable
を実装する形になりました。また、Error
の子供として TypeError
, ParseError
の2つが新設されています。
- Throwable
- Error
- TypeError
- ParseError
- AssertError(「PHP7調査(25)assert関数が実用的になった」参照)
- Exception
- ErrorException
- その他SPL例外
- Error
致命的エラーが例外になる仕組み
PHP7では、致命的エラーの多くがError
をスローするように実装が変わりました。Error
がトップレベルまで処理されなかった場合は、従来と同じ致命的エラーが表示されてPHPの処理が終了します。
このError
はException
の子供ではないため、PHP5向けコードのcatch (Exception $e)
ではキャッチされません。つまり、過去のコードをPHP7で動かした場合はPHP5と同じ動作をするように設計されています。
<?php
$foo = null;
try {
$foo->bar(); // 例外スロー、次のキャッチブロックに入る
} catch (Error $e) {
var_dump($e->getMessage());
}
echo "baz\n";
$foo->bar(); // 例外キャッチしていないため致命的エラーになる
$ php7 /tmp/foo.php
string(39) "Call to a member function bar() on null"
baz
PHP Fatal error: Uncaught Error: Call to a member function bar() on null in /tmp/foo.php:9
文法エラーも例外になった
PHP5までは文法エラーもキャッチ不可能なエラーでしたが、PHP7ではParseException
をスローするようになりました。これを利用して、PHP7からはinclude()
やeval()
で発生した文法エラーをキャッチできます。
<?php
try {
include("bar.php");
} catch (ParseError $e) {
var_dump($e->getMessage());
}
echo "baz\n"; // 致命的エラーより後まで処理が進む
$ php7 /tmp/foo.php
string(36) "syntax error, unexpected end of file"
baz
ただし、トップレベルのスクリプト自身の文法エラーはキャッチできません。
注意点1
残念ながら、全ての致命的エラーが例外になったわけではありません。
- スタートアップ処理でのエラーやメモリ不足など、真の致命的エラー
- 本来なら例外にすべきなんだけど、実装上の都合で例外に再実装するのがつらくて残っている致命的エラー
上記のような2種類の致命的エラーがまだ残っている状況です。PHPらしいといえばらしい気がしますが、ちょっと悲しいですね。
注意点2
警告・注意レベルのエラーはPHP7でも変わっていません。あらゆるエラーを統一的に扱いたい場合は、set_error_handler()
を使ってエラーハンドラで例外スローして、catch (Throwable $e)
でキャッチしましょう。
参照
本件、興味がある人はRFCを読んだ方がいいと思います。