PHPのエラーを表示するには、スクリプトの先頭にini_set('display_errors', '1');とerror_reporting(E_ALL);を書きます。前者はPHPにエラーメッセージを出力させ、後者は警告や非推奨の通知も含め、あらゆる種類のエラーを報告させます。
<?php
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
これを書いておけば、一度も代入していない変数を読むようなミスは、黙って失敗するのではなく、ファイル名と行番号付きのメッセージを表示します。
Warning: Undefined variable $total in /home/index.php on line 7
error_reporting()を引数なしで呼ぶと、現在の設定を数値で返します。Runを押すと、レベルを設定し、それを名前に戻して確認できます。
エラーの種類はそれぞれ1ビットなので、E_ALL & ~E_DEPRECATEDは「非推奨以外のすべて」という意味です。1行目をerror_reporting(E_ERROR | E_WARNING);に変えて、もう一度実行してみてください。
php.iniでエラーを表示する
スクリプト内の設定は、スクリプトが動き始めてからしか効きません。構文エラーも含め、すべてのスクリプトでエラーを表示するにはphp.iniを変更します(場所はphp --iniで確認できます)。
; Development
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
編集したらApacheかPHP-FPMを再起動します。php -Sの組み込みサーバーとコマンドラインは、起動のたびにファイルを読み込みます。php.iniにアクセスできないレンタルサーバーでは、mod_phpを使うApacheなら.htaccessの行も受け付けます。
php_flag display_errors on
php_value error_reporting -1
-1はすべてのビットを立てるので、E_ALLと同じです。
エラーレベル
| 定数 | 発生する原因 | スクリプトが止まるか |
|---|---|---|
E_ERROR | メモリ不足などの致命的な実行時エラー | 止まる |
E_PARSE | ファイルの構文エラー | 止まる |
E_WARNING | 未定義の変数や配列キー、includeするファイルがない | 止まらない |
E_NOTICE | 軽微な問題(PHP 8ではほとんど残っていない) | 止まらない |
E_DEPRECATED | 将来のPHPで使えなくなるコード | 止まらない |
E_USER_* | trigger_error()で自分で発生させるエラー | E_USER_ERRORのみ |
E_ALL | ここにないコアやコンパイルのエラーも含む全レベル |
PHP 8.0では、多くの古い警告が例外に変わりました。/でのゼロ除算や、組み込み関数に間違った型を渡すことは、今ではErrorのサブクラスを投げます。未定義の関数の呼び出しがPHP 7以降そうなっているのと同じです。3つともキャッチできます。
例外についてはtryとcatchで扱っています。警告、通知、非推奨は、今もここで説明するエラー報告の仕組みを通ります。
エラーを表示せずにログに記録する
公開中のサイトでは、訪問者にエラーメッセージを見せてはいけません。パス、SQL、コードが漏れてしまいます。代わりにログファイルに書き出します。
; Production
display_errors = Off
log_errors = On
error_log = /var/log/php/app-errors.log
error_reporting = E_ALL
本番でもerror_reporting = E_ALLのままにしておきます。レベルを下げてもエラーがなくなるわけではなく、気づけなくなるだけです。自分のメッセージもerror_log()で同じログに書けます。
<?php
error_log("Payment failed for order 1042");
テストしながらファイルを監視します:tail -f /var/log/php/app-errors.log。
set_error_handlerで警告を例外に変える
set_error_handlerは、警告、通知、非推奨に対するPHPのデフォルトの処理を自分の関数に置き換えます。よく使われるのは、すべてをErrorExceptionに変換するパターンです。こうすると、存在しない配列キーを読んだときもほかの例外と同じようにコードが止まり、キャッチできます。
ハンドラーは最初の引数としてエラーレベルを受け取るので、レベルごとに扱いを変えられます。次の例では、非推奨だけをログに残し、それ以外はすべて例外にします。
致命的なエラーと構文エラーのあとはスクリプトを続けられないので、ハンドラーには届きません。それらを記録するには、error_get_last()を読むシャットダウン関数を登録します。
error_get_lastで直前のエラーを読む
関数の中には、falseを返して理由を警告でしか伝えないものがあります。たとえば存在しないファイルに対するfile_get_contentsです。error_get_last()はその警告を配列で返すので、独自のメッセージを表示したいときに便利です。
呼び出しの前の@演算子は、その1つの式に限ってメッセージを抑制します。ここでのように直後に結果を確認する場合に限り、控えめに使ってください。@だけではバグを隠すだけです。
よくあるミス:エラー表示をオンにしたのにページが真っ白
ini_set('display_errors', '1')を書いたのにまだ真っ白なページが表示されるなら、おそらく構文エラーです。PHPは最初の行を実行する前にファイル全体を解析するので、;や}が抜けていると、ini_setが実行される前にすべてが止まります。ターミナルからファイルを確認すれば、メッセージは必ず表示されます。
php -l index.php
Errors parsing index.php
PHP Parse error: syntax error, unexpected token "," in index.php on line 2
行番号はPHPが解析をあきらめた場所を指していて、本当のミスの次の行であることがよくあります。直したら、No syntax errors detectedと表示されるまでphp -lを実行し直し、ページを再読み込みしてください。
よくある質問
PHPですべてのエラーを表示するには?
スクリプトのいちばん先頭にini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); error_reporting(E_ALL);を書くか、php.iniでdisplay_errors = Onとerror_reporting = E_ALLを設定し、WebサーバーかPHP-FPMを再起動します。
PHPのページが真っ白になるのはなぜですか?
display_errorsがオフの状態で致命的なエラーがスクリプトを止めたので、何も出力されなかったのです。エラー表示をオンにする(または、error_logが指すファイルをtail -fするなどしてエラーログを読む)と、メッセージと行番号がわかります。
ini_set('display_errors', 1)でも構文エラーが表示されないのはなぜですか?
構文エラーは、ファイルのどの行も実行される前にPHPを止めるので、ini_setの呼び出しが実行されません。php.iniでdisplay_errorsを設定するか、ini_setを書いた小さなファイルから壊れたファイルをincludeしてください。
本番環境でdisplay_errorsをオンにしてもいいですか?
いけません。エラーメッセージはファイルパスやコードの詳細を訪問者に見せてしまいます。本番ではdisplay_errors = Off、log_errors = Onとerror_logのパスを使い、問題が記録されるようにerror_reporting = E_ALLはそのままにしておきます。
error_reporting(0)は何をしますか?
スクリプトの残りの部分で、すべての種類のエラーの報告をオフにします。警告どころか致命的なエラーもメッセージを出さなくなります。問題を直すのではなく隠すだけなので、黙らせるよりログに記録するほうを選んでください。