uzullaがブログ

uzullaがブログです。

扱いづらいPHPのエラー処理を適当にいなす

追記

この記事のやり方でもいいっちゃいいのですが、PHP7以降では"set_error_handler"でエラーを例外に変換せず、"Error"例外を直接キャッチするほうがよりスマートだと思います。

PHP: PHP 7 でのエラー - Manual

なので「例外でキャッチできないエラーがある」というのがほぼなくなった感じですね。
(ただし、"Error"は"Exception"と兄弟関係なので、全部キャッチするつもりのcatch(\Exception $e)ではキャッチできない事に注意。)

追記終わり。

PHPはエラーが扱いづらい

いきなりですけど、PHPはエラーが扱いづらい言語だと思います。(おっと、最初の行からPHP Disだ、ブクマが稼げる)

「サーバーにPHPをアップロードして、ブラウザで試行錯誤する」というスタイルを実現するために、なにかあってもInternal Server Errorとしないで、自前で画面にエラーを出しちゃう。結果、色々タチが悪い。


でもエラーはもっと柔軟に扱いたい。回復出来るエラーは回復させたいですし、ちゃんとしたエラー画面を出したい。
いうても全部を昔ながらの返値をみるエラー判定(hogehoge or die的なもの含む)で書くと本当にコードが読みづらくて苦行だ。

なので、もっと積極的に例外をつかうほうが良いと思う。*1

DISCLAIMER

ここまでの話がよくわからん、画面にエラー出ないってなにそれ?という人は本エントリを参考にし無い方が良い。
画面に出力されるエラー、便利だしね、変にエラーを抑えて詰むよりも画面にエラーをだして解決した方が良い。


いってる事はわかるが同意できない、という人は理解した上だと思うので、それもまったく良いと思います。あくまで私の趣味ですから。

最初にオチ


本エントリはこのGistの焼き直しです。

「たとえば、エラー詳細を画面にだすのをやめる」

基本的に私は

ini_set("display_errors", 0);
ini_set("display_startup_errors", 0);
error_reporting(E_ALL);

を入れる事にしています。php.iniも最近はこれがデフォルトだったと思います。
エラーは全部の種類がほしいですし*2、そもそも画面にエラーを出されるのはダサい*3


ただ、これをやると当然画面にエラーがでなくなるので、エラーログを見る必要が出ますね。
このエントリを読むような人なら見れるでしょうが、宗教上や契約上の理由などでエラーログが見れない人はご愁傷様です。


あと、これをやっても途中まで出力してから死ぬとやっぱカッコ悪いので、Webアプリならob_start()とかで出力バッファリングもしましょう。*4

さて、一般的なエラーをどう扱うか

ここではエラーとかいてますが(実際エラーなので)、いわゆる例外で扱われるようなエラーをさしています。
以下のコードで標準的な関数のエラーを例外に変換する事ができます(その関数からスローされたように見えます)。

set_error_handler(function($errno, $errstr, $errfile, $errline){
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});

ErrorExceptionクラスはError用のExceptionクラスです。
http://www.php.net/class.errorexception
たとえばエラーコードとかは$e->getCodeでとれるので、泥臭い実装すれば色々できます。


コレ一ついれるだけでも深いコールからちゃんとキャッチできるので重要だと思います。*5


これでファイル操作等で一行一行エラー処理をかかずとも、まとめてエラー処理できるのではかどりますね。スローしつつerror_log()出力するのも良い。


ちなみに、最近のWAFでは最初からこれが設定されています。PHPerスキルのコピペをしたらおかしくなった!という場合は疑いましょう。

例外でキャッチできないエラーがある

ただ、例外ではキャッチできないエラーもあります。
たとえば存在しない関数をコールすると、上記コードはコールされずに死にます。
前述の画面にエラーを出さない系を設定していると「白い画面」が出ます、お客様がおこですね。


「なんだよPHP駄目じゃん!すっごい不便じゃん!」と思うでしょう、俺もそう思う。
ただ、そのFATAL系のエラーでも、多少リカバーする方法はあり、register_shutdown_function()を使います。

!注意!以下のコードを理解せずコピペしてつかってはいけません(理由は後述)!!

register_shutdown_function(function(){
    $e = error_get_last();
    if( $e['type'] == E_ERROR ||
        $e['type'] == E_PARSE ||
        $e['type'] == E_CORE_ERROR ||
        $e['type'] == E_COMPILE_ERROR ||
        $e['type'] == E_USER_ERROR ){
        // お好きな処理を書く
        echo "致命的なエラーが発生しました。\n";
        echo "Error type:\t {$e['type']}\n";
        echo "Error message:\t {$e['message']}\n";
        echo "Error file:\t {$e['file']}\n";
        echo "Error line:\t {$e['line']}\n";
    }
});

このregister_shutdown_function()で「実行終了する時」に処理を差し込めます。
つまり「正常終了の時」もこれがコールされる、まあやったらわかる。
なのでイチイチエラーコードを取得して、そのエラーコードをみて挙動を変えています。

エラーコードはこちらを見てください。
http://www.php.net/manual/ja/errorfunc.constants.php


ダサい、どうにも秘伝のタレ感ハンパないコードですが、これで例外で拾えない致命的なエラーもトラップできます。
例外にできないの悔しいけど、ここに入ってくる=PHPのコードがおかしいレベルなので、諦めるしかない。
っていうか、ここに来る時点で、9割自分のコードが駄目なのであり、自分が悪い。


「立派なプロダクト」なら、ここで「ユーザーに親切なエラーメッセージを出す」必要があると「私は」考えています。

ちなみに、わかってるとおもいますがこれはあくまで解説用サンプルなんで、エラー詳細だしてますけど、こんなのぶっこんだらDisplay_errors指定してるのとかわりありませんからね。


あと、「無駄な処理だな、遅そうだ」というご意見わかりますが、まあ一般的な処理の最後にこの程度追加しても誤差でした。

まとめ

PHPでは良い感じにエラーを取り回すこと一つ取っても結構ダサくてさみしい。


「画面に出したらべんり!さいこう!」というのは、もうPHPにBuiltin Serverがある時代ではいらないと思うし(本番ではオフるでしょ)、
「白い画面」をだしたらお客様はおこであり、本当につらいのでがんばってへらしていきたいと思う(という強い心がけ)

もっと良いやりかたをご存じでしたらば、教えてください。

なんかエラそうな事かきましたけど、*6まだコードの中に

@unlink("not_found_file_name");

とかかいちゃう事がある程度には低俗です*7

*1:「例外厨か、ご愁傷様」というご意見については、まあそうだと思う

*2:Depricatedは潰すべき、Noticeがでるのはおかしい

*3:ファイルのDIRとかみえると「ヒャッハー」って言われるし

*4:まーWAFつかったり、ヘッダを出力すること考えたら、最初からやってるでしょうが

*5:もうこういう挙動を、サラっと一行で有効にできるようにしてよって思いますが…

*6:まあ削除しそこねても大勢に影響はないとか、「運用で」マズイファイルを先に削除しちゃったんだ、みたいな時とかを考えると

*7:「こ、これはビタミン剤じゃ…」