PHP は Web 開発に便利なプログラミング言語です。Web が前提となっていることもあり、PHP に与えられた外部入力に対してのエラー、警告等を PHP 自身が自動で出力してくれます。これは便利な機能なのですが以下の 3 点が合わさることにより絶妙に操作しにくくなっています。
- PHP 組み込み機能のエラー処理にはエラー報告と例外処理が存在する
- ソースコード中で取り扱いやすいのは圧倒的に例外処理(catch できたり、スタックトレースを付けれたりできます)
- 外部入力に対するエラー、警告はソースコードの処理に入った時点で in Unknown on line 0 で発生したエラーとして報告される
PHP: 実行時設定 – Manual#error-reporting
PHP: エラー – Manual
PHP: 例外(exceptions) – Manual
ソースコードに記述された PHP の組み込み関数実行時のエラー報告に関しては次の様にソースコード内でErrorException
を投げるset_error_handler
を記述することで完全に例外処理化できます。
<?php // https://www.php.net/manual/ja/class.errorexception.php より引用 function exception_error_handler($severity, $message, $file, $line) { if (!(error_reporting() & $severity)) { // このエラーコードが error_reporting に含まれていない場合 return; } throw new ErrorException($message, 0, $severity, $file, $line); } set_error_handler("exception_error_handler"); /* 例外を発生させます */ strpos(); /** 出力 Fatal error: Uncaught exception 'ErrorException' with message 'strpos() expects at least 2 parameters, 0 given' in /home/bjori/tmp/ex.php:12 Stack trace: #0 [internal function]: exception_error_handler(2, 'strpos() expect...', '/home/bjori/php...', 12, Array) #1 /home/bjori/php/cleandocs/test.php(12): strpos() #2 {main} thrown in /home/bjori/tmp/ex.php on line 12 */
PHP: set_error_handler – Manual
PHP: ErrorException – Manual
しかし困ったことに外部入力に対してのエラー、警告によるエラー報告はソースコード内のset_error_handler
関数実行前に発生しており、ソースコード内でset_error_handler
を記述してエラー報告を操作する方法ではこれを例外化できません。
例外化できないパターンでは想定外の結果を引き起こすシナリオがいくつかあります。想定外になるのはおおよそ発生していても処理が続行されるレベル(E_WARNING, E_NOTICE など)のエラーが起きている時です。例えば、max_input_vars に引っかかった時です。max_input_vars は外部からの入力変数の最大値でこれを超えた場合、超えた分の外部入力をなかったものとして処理を続行します。これによって不完全な入力をそのまま処理続行してしまいます。もし不完全な入力がソースコード的には正しい入力であったりすると途中までしか処理されていないのに正常終了となり、異常な状態ができあがります。
PHP: 定義済み定数 – Manual
PHP: 実行時設定 – Manual#max-input-vars
このエラーを例外にするには既に発生したエラーを見つけて、そのエラーを元に投げる必要があります(こうするとソースコード内でキャッチされないにしても、不正な処理を打ち切るという点でメリットがあります)。これは次のコードで実現できます。
if (error_get_last()) {// 最後に発生したエラーが存在するならば $error = error_get_last(); // 最後に発生したエラーをもとに throw ErrorException throw new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']); }
これだけです。これを例外ハンドリングの定義の後に書くかソースコードのエントリーポイントに書くかすればよいです。前者は危険だと分かった処理をある程度走らせることになりますがエラー内容が恐らく見やすくなり、ロギングもされます(例外ハンドリングの実装依存)。後者はがっつり打ち切ります。さならがら nginx 等の web サーバで処理を切った時の様になります。