概要
複数のエラーを補足していって、まとめて表示するなどの処理をしたい場合がある。たとえばユーザーIDとパスワードの妥当性をチェックし、それぞれのエラーを全て表示したいような場合。
このようなケースで例外を使うと、例外が発生した場合や意図的にスローした場合、その時点で例外処理に移行するので、複数のエラーを扱い難い。
Exception::getPrevious()
Exceptionクラスのコンストラクターは、3つの引数をとることができる。
public __construct (string $message = "", int $code = 0, Throwable $previous = null )
3つ目の引数はThrowableで、この引数にExceptionオブジェクトを指定すると、新たに生成されたオブジェクトの前に発生したExceptionオブジェクトがスタックに保持される。
$new_ex = new Exception('this exception', 0, $previous_ex)
最終的に得られたExceptionオブジェクトのスタック上にそれ以前のExceptionオブジェクトがある場合、getPrevious()メソッドで直前のオブジェクトが得られる。直前のExceptionオブジェクトがない場合はgetPrevious()の戻り値はnullとなる。
基本形
これらのことを利用して、複数の例外を途中で抜け出さずに蓄積する方法は以下のようになる。
- 2行目の初期化は、1番目・・・n番目のどこが最初の例外をスローするかが確定していないため必須。
- 17~19行目、適切な位置で例外をスローしないと反映されないが、例外が発生していない場合はスローしない
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | try {   $exception = null;   if (1つ目の条件式) {     1つ目の実行文;   } else {     $exception = new Exception('1つ目の例外', 0, $exception);   }   if (2つ目の条件式) {     2つ目の実行文;   } else {     $exception = new Exception('2つ目の例外', 0, $exception);   }   .....   if (isset($exception) {     throw $exception;   } } catch (Exception $e) {   do {     $e->getMessage()などを使った各例外に対する処理;   } while ($e = $e->getMessage()); } | 
実行例
以下は実行例。1つ目と3つ目の条件式がfalseなので例外が積み重ねられ、2つ目はtrueなのでそのまま表示、そのあとに積まれた例外が表示される。
| 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 | <?php try {   $exception = null;   if (false) {     echo '1つ目は成功<br>';   } else {     $exception = new Exception('1つ目は失敗', 0, $exception);   }   if (true) {     echo '2つ目は成功<br>';   } else {     $exception = new Exception('2つ目は失敗', 0, $exception);   }   if (false) {     echo '3つ目は成功<br>';   } else {     $exception = new Exception('3つ目は失敗', 0, $exception);   }   if (isset($exception)) {     throw $exception;   } } catch (Exception $e) {   do {     echo $e->getMessage(), '<br>';   } while ($e = $e->getPrevious()); } // 2つ目は成功 // 1つ目は失敗 // 3つ目は失敗 | 
関数化・配列への格納
Exceptionオブジェクトの積み込みと、例外発生時のみの例外スロー、スタックされた例外に対する操作を関数化しておくと、再利用しやすく可読性も上がる。
18行目で配列の順序を反転し、スタックに積まれた順番(LIFO)からFIFOのリスト順にしている。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function stack_exception($message, $exception) {   return new Exception($message, 0, $exception); } function throw_conditionally($exception) {   if (isset($exception)) {     throw $exception;   } } function get_error_messages_from_exception($exception) {   $error_messages = [];   do {     $error_messages[] = $exception->getMessage();   } while ($exception = $exception->getPrevious());   return array_reverse($error_messages); } | 
これらの関数を使って先と同じ処理をするコード
| 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 | try {   $exception = null;   if (false) {     echo '1つ目は成功<br>';   } else {     $exception = stack_exception('1つ目は失敗', $exception);   }   if (true) {     echo '2つ目は成功<br>';   } else {     $exception = stack_exception('2つ目は失敗', $exception);   }   if (false) {     echo '3つ目は成功<br>';   } else {     $exception = stack_exception('3つ目は失敗', $exception);   }   throw_conditionally($exception); } catch (Exception $e) {   $error_messages = get_error_messages_from_exception($e); } echo '<pre>', print_r($error_messages, true), '</pre>'; | 
関数での例外発生のトレース
最初の関数で発生した例外への過程をスタックに残すには、以下のように関数内で前の例外を登録してスローする。
| 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 | <?php function a() {   throw new Exception('関数aで例外発生');   echo '関数aの処理<br>'; } function b() {   try {     a();     echo '関数bの処理<br>';   } catch (Exception $e) {     throw new Exception('関数bで以下の例外発生', 0, $e);   } } function c() {   try {     b();     echo '関数cの処理<br>';   } catch (Exception $e) {     throw new Exception('関数cで以下の例外発生', 0, $e);   } } try {   c(); } catch (Exception $e) {   do {     echo $e->getMessage(), '<br>';   } while ($e = $e->getPrevious()); } // 関数cで以下の例外発生 // 関数bで以下の例外発生 // 関数aで例外発生 |