Today Fortkle Learned.

知らないことの方が多いので今更調べています。さらに一歩先に行けたら嬉しいです。

PHPのエラーハンドリングとロギング

エラー表示/非表示

PHPのエラー

PHPのエラーはそのまま画面に出る。たまにPHPアプリケーションで「白い画面」になることがあるが、これは「エラーが発生して処理が終了しているがエラーが非表示設定なので何も出ていない状態」であることが多い。 白い画面を避けたいのであれば、エラーを非表示にしつつ、適切なハンドリングを行う必要がある。

まずは「エラーを表示するかどうか」と「どのエラーレベルのエラーを出すか」を設定する。

エラー表示/非表示の設定

エラーを表示する/しないを設定する方法はたくさんある。どれか1つの設定でOKだ。 このとき、 iniset()>.htaccess>php.ini という優先順位になるようだ。

基本的には ini_set()を使うことが確実でおすすめである。

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

このとき、 display_errorsPHPスクリプト実行中のエラー表示の可否を決めるが、display_startup_errorsスクリプト実行前の、初期化時に発生するエラーの表示可否を決める。

コード上で設定

<?php
// エラーを出力する
ini_set('display_errors', "On");
?>
<?php
// エラーを出力する
ini_set('display_errors', 1);
?>

httpd.confや.htaccessで設定

php_flag display_errors off

php.iniで設定

display_errors = Off

エラーレベル

PHPのエラーレベルは、「定義済み定数」として定義されている。 PHPのバージョンによってあるものとないものがあるため注意が必要である。以下に代表的な定義済み定数を列挙する。

  • E_ALL

    • サポートされる全てのエラーと警告。 PHP 5.4.0 より前のバージョンでは、E_STRICT レベルのエラーは除く。
  • E_ERROR

    • 重大な実行時エラー。これは、メモリ確保に関する問題のように復帰で きないエラーを示します。スクリプトの実行は中断されます。
<?php
// 未定義の関数を呼ぶ
hoge();
  • E_WARNING
    • 実行時の警告 (致命的なエラーではない)。スクリプトの実行は中断さ れません。
<?php
// 存在しないリソースを参照した場合
file_get_contents('file not exist');
  • E_NOTICE
    • 実行時の警告。エラーを発しうる状況に遭遇したことを示す。 ただし通常のスクリプト実行の場合にもこの警告を発することがありうる。
<?php
// 未定義定数による警告の生成 
$t = I_AM_NOT_DEFINED;
  • E_STRICT
    • コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する(※PHP7で廃止)。
<?php
class Foo {
    public function method() {}
}
class Bar extends Foo {
    public function method($arg) {}
}
  • E_DEPRECATED
    • 実行時の注意。これを有効にすると、 将来のバージョンで動作しなくなるコードについての警告を受け取ることができる。
<?php
// 非推奨となった関数を使用
if (ereg("\.php$", 'hoge.php')) { 
}
  • E_USER_ERROR
    • ユーザーによって発行されるエラーメッセージ。 E_ERROR に似ているがPHPコード上で trigger_error() 関数を 使用した場合に発行される点が違う。

その他の定義済み定数は以下のページで確認できる。 PHP: 定義済み定数 - Manual

エラーレベルの分類

前述したエラーレベルは挙動や内容によって以下のように分類できる。

  • 実行が中断されるエラー
    • Fatal:致命的なエラー
      • E_ERROR
  • 実行は継続するが警告があるエラー
    • Warning : 警告
      • E_WARNING
    • Notice:注意
      • E_NOTICE
  • 実行されないエラー
    • シンタックスエラー
      • E_PARSE
    • コーディング規約に関するエラー
      • E_DEPRECATED
      • E_STRICT ※PHP7で廃止
  • 自身でエラーを定義する
    • ユーザ定義の致命的エラー
      • E_USER_WARNING
      • E_USER_NOTICE
    • ユーザ定義の将来廃止予定機能の警告
      • E_USER_DEPRECATED

エラー出力レベルの設定

次に「どのエラーレベルのエラーを出すか」を設定する。 PHPにはエラーレベルがたくさんあり、「これは出力する、それは出力しない」といった細いカスタマイズが可能である。

基本的には error_reporting()もしくは ini_set('error_reporting', "エラーレベル"); を使うのがおすすめである。

コード上で設定

<?php
error_reporting(E_ALL & ~ E_DEPRECATED & ~ E_USER_DEPRECATED & ~ E_NOTICE);
?>
<?php
ini_set('error_reporting', E_ALL);
?>

httpd.confや.htaccessで設定

php_value "error_reporting" "E_ALL & ~E_NOTICE & ~E_DEPRECATED"

php.iniで設定

error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED

よくあるエラー出力レベルの設定

E_NOTICE(注意メッセージ)以外の全てのエラーを表示する。

error_reporting = E_ALL & ~E_NOTICE

E_NOTICE(注意メッセージ)とE_DEPRECATED(推奨エラー)は表示しない。

error_reporting = E_ALL & ~ E_DEPRECATED & ~ E_USER_DEPRECATED & ~ E_NOTICE

エラーハンドリング

ここまででエラーを表示させたり、特定のレベルのものだけ非表示にする方法を見てきた。

次はエラーのときにどういった処理を行うかを設定する方法だ。

デフォルト状態では、PHPの組み込み関数の大半はエラー時に例外を発生させずエラーメッセージを表示するだけである。そのため例外を発生させたり何らかの方法を用いてエラーを検知し、処理を行う必要がある。

よく行われるのは「PHPの標準のエラーを例外に変換して投げる」である。 こうすると例外をcatchで捕捉すればエラー時の処理を書くことができる。

  • PHPの標準のエラーを例外に変換して投げる
// エラー時に例外をスローするようにコールバック関数を登録
set_error_handler(function($errno, $errstr, $errfile, $errline){
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

PHP5のFatal Errorの不便な点とPHP7での進化

PHP5のFatal Error(致命的エラー)には不便な点がある。 それは、set_error_handler やその他の方法でエラーハンドリングできず、必ず終了してしまう点である。つまり前述の方法で例外に変換できないということである。

PHP5でFatal Errorもハンドリングするためには、 error_get_last で直近のエラーを取得して 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";
        }
    }
);

※ エラー詳細出しているが、display_errors指定してるのと変わらないのでもし使うならちゃんと隠す。

ちなみに、PHP7ではFatal Error(致命的エラー)が例外として実現されるようになり、エラーハンドリングの自由度が格段に上がった。 詳細は下記を参照。
お前は PHP 7 における Fatal Error / Catchable Fatal Error / Error / ErrorException / Exception の違いを言えるか? - Qiita

参考

PHPのエラー表示設定について - Qiita
扱いづらいPHPのエラー処理を適当にいなす - uzullaがブログ
PHPの組み込み関数で例外を発生させる方法 | 徳丸浩の日記
PHPのエラーの種類 - Qiita
PHP7調査(23)致命的エラーが例外としてキャッチできるようになった - Qiita
set_error_handlerではfatal errorはハンドリングしてくれない - Qiita

ロギング

エラーをログに出力したい場合も表示非表示をハンドリングしたときと同様に ini_setを用いる。

ini_set("log_errors", 1);
ini_set("error_log", "/var/log/php_error.log");

この設定が指定されていなかった場合はSAPIエラーロガーに送信される。例えば Apache のエラーログ、 あるいは CLI なら stderr に送信される。

もし、ログが出力されない場合、出力先フォルダのパーミッションがおかしい場合が多い。