PHP7で変わる例外処理について

  • 24
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

PHPにおける例外処理はPHP5からでPHP4以前には実装されていませんでした。
その頃は自前で対処するしかなく予想外の動作に対応しきれないなどありましたが、TryCatchブロックの実装により改善されつつあります。
しかし、捕捉できないエラーがあるといった問題がまだ残っていました。
今回のPHP7では、そういった問題への対処も行われるようなので、どのように改善されるのか調べてみました。

既存のエラー種別

エラー種別としては以下。
こちらだと日本語なので分かりやすいです。

// Fatal errors
E_ERROR
E_CORE_ERROR
E_COMPILE_ERROR
E_USER_ERROR

// Recoverable fatal errors
E_RECOVERABLE_ERROR
// Parse error
E_PARSE

// Warnings
E_WARNING
E_CORE_WARNING
E_COMPILE_WARNING
E_USER_WARNING

// Notices etc.
E_DEPRECATED
E_USER_DEPRECATED
E_NOTICE
E_USER_NOTICE
E_STRICT

課題①

上記にある4つのFatal errorsに関してはTryCatchで捕捉できません。直ぐにスクリプト修了処理が走ってしまうためです。
これについてはregister_shutdown_function関数を使うというのが解決法です。

register_shutdown_function(function() { var_dump(error_get_last()); });

$null = null;
$null->foo();

// shutdown function output:
array(4) {
  ["type"]=> int(1)
  ["message"]=> string(47) "Call to a member function foo() on a non-object"
  ["file"]=> ...
  ["line"]=> ...
}

しかし、これではfinallyブロックが使えません。
リソースの解放処理など必ず実行したい処理に対応できないという問題があります。

課題②

Classの__destructも同様に実行されない場合があるため、ロックされたままのリソースが発生してしまいます。

class LockManager {
    private $lock;
    public function __construct(Lock $lock) {
        $this->lock = $lock;
        $this->lock->acquire();
    }
    public function __destruct() {
        $this->lock->release();
    }
}

function test($lock) {
    $manager = new LockManager($lock); // acquire lock

    doSomething();

    // automatically release lock via dtor
}

この場合ならdoSomething()でFatalErrorが発生した場合に起こります。

新たなエラークラス

このような問題を解決するために、PHP7からはErrorクラスが実装されました。
http://php.net/manual/ja/class.error.php
Exceptionクラスは継承していないとのことなので、こんな感じで使ってみました。

try {
    call_method(null); // oops!
} catch (Exception $ex) {
    echo "Exceptionをキャッチ: {$ex->getMessage()}\n";
} catch (Error $er) {
    echo "Errorをキャッチ: {$er->getMessage()}\n";
} finally {
    echo "finally!!\n";
}

結果は

Errorをキャッチ: Call to undefined function call_method()
finally!!

という感じで無事に捕捉できました!

まとめ

前置きばかりながくなりましたが、以上が例外処理に関して分かったことです。
PHP7へ移行する際に例外処理の範囲を広げるにはcatch (Error $er){}といったブロックを追加していく必要がありそうです。
ただこれによってエラーへの対処方法がやりやすくなりそうですね。
全てのFatalErrorについて確認していないため、どの程度がカバーできるのか分からないので今後も色々調べてみたいと思います。

謝罪

AdventCalendarの投稿、遅れてしまい申し訳ありませんでした。

この投稿は PHP Advent Calendar 20152日目の記事です。