Laravelでスクリプトを停止させずE_NOTICEのログを残す
LaravelでE_NOTICEの発生でスクリプトは止めたくないが、ログには残しておきたいという場合の対処法。
PHPではerror_reporting設定で出力するエラーの種類を変更することができます。 Laravelではデフォルトでerror_reporting(-1)となっており全エラーを報告するようになっています。Laravelではエラーが報告されると(E_NOTICEも含め)全て例外となり、スクリプトの実行が停止してエラーページが表示されます。
このため、PHPの本来の動作としてはE_NOTICE, E_WARNINGのエラーについてはスクリプトの実行は中断されませんが、LaravelにおいてはE_NOTICE, E_WARNINGでもスクリプトの実行が停止してしまいます(図1)。

E_NOTICEについては、配列に未定義のindexでアクセス($foo['bar'])した場合など、軽度のもので発生するので、これでスクリプトが停止してしまうのはキツいということで、AppServiceProviderなどでerror_reporting(E_ALL & ~E_NOTICE)のように設定しておくこともあると思います。
ただし、これだとE_NOTICE自体が報告されなくなるので、E_NOTICEが発生しているかがわからなくなり、E_NOTICEを修正していくのが難しくなります。開発環境と本番環境で設定を分けて、開発環境ではerror_reporting(E_ALL)で動作させておけばいいのですのが、ここでは、E_NOTICEが発生してもスクリプトの実行は止めず、ログには残してE_NOTICEが発生したことを確認できるようにしてみます。
実装例は以下のようになります。
実装例
class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { // E_NOTICEはLaravelに渡さないように設定 $this->takeAwayErrors([E_NOTICE]); : 略 } : 略 private function takeAwayErrors($stealLevels) { $laravelHandler = set_error_handler(function () {}); restore_error_handler(); $handler = function ($level, $message, $file = '', $line = 0) use ($stealLevels, $laravelHandler) { if (!is_callable($laravelHandler)) { // phpのエラーハンドラに渡す return false; } // $stealLevelsに指定されたエラーレベルならphpのエラーハンドラに渡す if (array_search($level, $stealLevels) !== false) { return false; } // laravelのエラーハンドラへ // error_reporting()に該当するエラーは全て例外となる return call_user_func( $laravelHandler, $level, $message, $file, $line ); }; set_error_handler($handler); }
この例ではAppServiceProviderに実装しています。
takeAwayErrors()で何をやっているかというと、ユーザー定義のエラーハンドラを作成し、指定レベル($stealLevels)のエラーについてはLaravelのエラーハンドラに渡さずにPHPのデフォルトのエラーハンドラに渡すようにしています。それ以外のエラーについては、元々登録されていたLaravelのエラーハンドラに渡し例外となります。
Laravelから横取りするエラーの種類は以下のように配列で指定します。
$this->takeAwayErrors([E_NOTICE]);
これで、E_NOTICEについてはPHPのデフォルトのエラーハンドラに渡されるようになるので、ログのみ取られてスクリプトの実行は中断されなくなります(*1)。
なお、ログはLaravelのstorage/logsディレクトリではなくphpのerror_log設定で指定したファイルに出力されます。
E_WARNINGでも停止させたくない場合は以下のように指定します。
$this->takeAwayErrors([E_NOTICE, E_WARNING]);
PHP8では多くのE_NOTICEエラーがE_WARNINGに変更されたので、このような設定にする必要が出てくるかもしれません。
なぜこんなことをしたかというと、以前引き継いだシステムがE_NOTICEを無視していたので、動作中のシステムを動かしながらE_NOTICEの発生状況を確認したかったためです。これでシステムを動かしながらE_NOTICEの発生を監視&修正していくことができます。 error_reporting(E_ALL & ~E_NOTICE)で無条件に無視し続けるよりはいいのではないでしょうか。
ちなみに、Laravelの標準のエラーハンドラはIlluminate/Foundation/Bootstrap/HandleExceptionsクラスのhandleError()メソッドにあります。error_reporting()にマッチするエラーは全て例外を投げる動作になっています。
Laravelの標準のエラーハンドラ
class HandleExceptions { : public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } }
(*1) error_reporting(E_ALL)のようにE_NOTICEを含めた設定になっている必要があるのに注意。error_reporting(E_ALL & ~E_NOTICE)になっているとPHPのデフォルトのハンドラでも無視されるのでログには残りません。
投稿日:2021/01/22 00:50