エラー出力を見やすく整形してみる(set_error_handler、register_shutdown_function)

不必要なエラー出力はセキュリティ上のリスクがあるため、公開する運用システムでは出力してはいけません。これはPHPが出力するエラー内容にプログラムやシステムなどのデータが含まれているためです。以下のコードでは基本的に全てのエラーが表示されてしまうため、条件分岐でコード全体の出力をIPやログイン情報などを使って特定の条件のみに制限したり、不要なものを取り除く、もしくはコードそのものを書き換えて使用すべきです。このコードはテスト段階で通常のエラー出力では省略されているものを視覚的に見やすくしようと意図して作成したものです。また、switch文に設定しているエラーレベルのcaseで必要ないものは削除しても問題ありません。更に、switch文内の日本語に自分用の内容を追記するとよりエラー内容が分かりやすくなると思います。尚、下のコードでは処理が継続できるエラーは「set_error_handler」で補足され、逆に処理が継続できないエラーは「register_shutdown_function」で補足されるはずです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 詳細なエラー情報を表示するユーザー定義関数を作成
function sampleFunc($code,$file,$line,$message){
switch($code){
case'1': $level='E_ERROR'; $levels="重大な実行時エラー。これは、メモリ確保に関する問題のように復帰で きないエラーを示します。スクリプトの実行は中断されます。未定義の関数を呼び出したり、同じ関数名を二重定義した時などに発生します。";break;
case'2': $level='E_WARNING'; $levels="実行時の警告 (致命的なエラーではない)。スクリプトの実行は中断さ れません。 ";break;
case'4': $level='E_PARSE'; $levels="コンパイル時のパースエラー。パースエラーはパーサでのみ生成されま す。 ";break;
case'8': $level='E_NOTICE'; $levels="実行時の警告。エラーを発しうる状況に遭遇したことを示す。 ただし通常のスクリプト実行の場合にもこの警告を発することがありうる。";break;
case'16': $level='E_CORE_ERROR'; $levels="PHPの初期始動時点での致命的なエラー。E_ERRORに 似ているがPHPのコアによって発行される点が違う。";break;
case'32': $level='E_CORE_WARNING'; $levels="(致命的ではない)警告。PHPの初期始動時に発生する。 E_WARNINGに似ているがPHPのコアによって発行される 点が違う。 ";break;
case'64': $level='E_COMPILE_ERROR'; $levels="コンパイル時の致命的なエラー。E_ERRORに 似ているがZendスクリプティングエンジンによって発行される点が違う。";break;
case'128': $level='E_COMPILE_WARNING'; $levels="コンパイル時の警告(致命的ではない)。E_WARNINGに 似ているがZendスクリプティングエンジンによって発行される点が違う。 ";break;
case'256': $level='E_USER_ERROR'; $levels="ユーザーによって発行されるエラーメッセージ。E_ERROR に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。 ";break;
case'512': $level='E_USER_WARNING'; $levels="ユーザーによって発行される警告メッセージ。E_WARNING に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。 ";break;
case'1024': $level='E_USER_NOTICE'; $levels="ユーザーによって発行される注意メッセージ。E_NOTICEに に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。";break;
case'2048': $level='E_STRICT'; $levels="コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する。";break;
case'4096': $level='E_RECOVERABLE_ERROR';$levels="キャッチできる致命的なエラー。危険なエラーが発生したが、 エンジンが不安定な状態になるほどではないことを表す。 ユーザー定義のハンドラでエラーがキャッチされなかった場合 (set_error_handler() も参照ください) は、 E_ERROR として異常終了する。 ";break;
case'8192': $level='E_DEPRECATED'; $levels="実行時の注意。これを有効にすると、 将来のバージョンで動作しなくなるコードについての警告を受け取ることができる。 ";break;
case'16384':$level='E_USER_DEPRECATED'; $levels="ユーザー定義の警告メッセージ。これは E_DEPRECATED と同等だが、 PHP のコード上で関数 trigger_error() によって作成されるという点が異なる。 ";break;
case'32767':$level='E_ALL'; $levels="サポートされる全てのエラーと警告。 PHP 5.4.0 より前のバージョンでは、E_STRICT レベルのエラーは除く。 ";break;
}
$content = <<<EOL
<style>table,table.error td{border:solid 1px #ccc;font-size:13px;}table.error td{padding:3px 5px;}</style>
<table class="error">
<tr>
<td>エラーの種類 [{$code}]:{$level}</td>
<td>{$levels}</td>
</tr>
<tr>
<td>エラーファイル</td>
<td>{$file}</td>
</tr>
<tr>
<td>エラー行</td>
<td> {$line}行目</td>
</tr>
<tr>
<td>伝言</td>
<td class="error-td">{$message}</td>
</tr>
</table>
EOL;
return $content;
}
ini_set("display_errors", 0);
ini_set("display_startup_errors", 0);
error_reporting(E_ALL);
// シャットダウン時に呼び出すコールバック関数を登録
register_shutdown_function(
function(){
$error = error_get_last();
echo sampleFunc($error['type'],$error['file'],$error['line'],$error['message']);
});
// エラーイベントを例外ハンドラに渡す
set_error_handler(function($errno, $errstr, $errfile, $errline){
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});
// try
try{
// テスト用
print $a;
// trigger_error("エラーハンドラテスト", E_USER_WARNING);
// $get_file = file_get_contents('file_name');
// abcd();
}catch(Exception $e){
echo sampleFunc($e->getCode(),$e->getFile(),$e->getLine(),$e->getMessage());
}finally{
}
実行結果:
エラーの種類 [8]:E_NOTICE 実行時の警告。エラーを発しうる状況に遭遇したことを示す。 ただし通常のスクリプト実行の場合にもこの警告を発することがありうる。
エラーファイル /・・・(省略).php
エラー行 61行目
伝言 Undefined variable: a

sampleFuncユーザー定義関数

sampleFunc()は、詳細なエラー出力のための情報を格納したユーザー定義関数です。52行目のregister_shutdown_function関数のコールバック関数内と66行目のcatchブロック内での結果表示に使っています。関数名は変更して構いませんが、上記ブロックで使われている名前も同じものにしないと動作しません。

「display_errors」と「display_startup_errors」

ini_set()は、PHPプログラムの全体的な動作や環境を設定するファイルであるphp.iniの設定を変更できますが、全てのオプションが変更できる訳ではありません。第一引数で「display_errors」を指定した場合、第二引数で0を設定するとエラーを表示しません。1を設定すると表示します。第一引数で「display_startup_errors」を設定して、第二引数で1を指定するとdisplay_errorsでも出力されない起動シーケンスにおいて発生するエラーを表示します。0にすると表示されません。

「error_reporting()」

47行目のerror_reporting()は出力するエラーの種類を登録します。引数に使えるエラーの種類は、E_ERRORやE_WARNINGなど定義済みの定数から選びます。E_ALLを指定すると、サポートされる全てのエラーと警告を表示します。

「register_shutdown_function()」

49行目のregister_shutdown_function()はスクリプトの処理完了後もしくはexit()が呼び出された時に実行するコールバック関数を登録して使います。複数回に渡って呼び出すことができ、登録順に関数が呼び出されます。また、コールバック関数から出力を送信することが可能で、出力バッファにもアクセスできます。

「error_get_last()」

51行目のerror_get_last()は最後に発生したエラーを連想配列で取り出します。キーは「type」「message」「file」「line」です。

エラーハンドラの登録

55行目から57行目にかけては、set_error_handler()でエラーハンドラを定義し、エラー処理そのものは例外ハンドラに渡して実行しています。56行目のErrorExceptionは、エラーを例外に変換する組み込みクラスであり、エラーハンドラと例外ハンドラを別々に処理しない場合に使われます。

try catch

例外処理では、try、throw、catch、finallyといったものが利用でき、catchは複数定義できます。また、finallyブロックでは例外の存在に関係なく必ず実行されます。throwステートメントはtryブロック以外で例外が投げられたケースでも使われます。

tryブロックでは例外がスローされるとそれ以降の実行は中止され、catchブロックに移動して処理を続行します。その場合、組み込み例外オブジェクトのExceptionを使って、処理を行うことができます。

trigger_error

62行目はtrigger_error(”エラーメッセージ”, エラーレベル)の書式で使われます。これはエラーメッセージをわざと表示させるものです。エラーレベルは、E_USER_ERROR、E_USER_WARNING、E_USER_NOTICE、E_USER_DEPRECATEDのいずれかを指定できます。また、user_errorで記述されることもありますが、これはtrigger_errorの別名(エイリアス)です。


尚、処理が継続できるエラーは「set_error_handler()」で補足できますが、Fatal Errorは処理が継続できないため、「register_shutdown_function()」で補足する方法が知られています。また「エラー行」は参考程度に見るべきであって、必ずしも出力されるエラー行に原因があるとは限りません。そしてeval関数を使っている場合で、かつ呼び出したコード内においてエラーがあれば、その呼び出したコード内でのエラー行を出力します。しかし、通常のエラー行出力と同様に当てにならないことがあるかも知れません。