PHPの例外(Exception)

PHPは、例外処理をサポートしています。例外とは、プログラムの実行中に何らかの異常が発生することを言いますが、開発者が明示的に例外を発生させることもできます。例外処理とは、実行中に発生した例外を捕捉し、それに対応する処理を行うことです。

例外のスロー(throw)

明示的に例外を発生させることをスロー(投げる)と言います。例外を投げるには、throwキーワードを使用し、ExceptionクラスまたはExceptionクラスのサブクラスのオブジェクトを渡します。下記の例では、明示的に例外をスローしていますが、例外処理を記述していないので、Fatal errorとなります。例外処理を行うには、例外を捕捉する必要があります。

PHP

<?php
throw new Exception('例外が発生しました');
/*
出力:Fatal error: Uncaught exception 'Exception' with message '例外が発生しました'
*/
?>

例外の捕捉(try catch)

例外は、下記の書式で捕捉することができます。まず、tryブロック内で例外が発生する可能性のある処理を行います。例外が発生した場合はそこで処理を中止し、発生した例外に対応するcatchブロックの処理を実行します。

PHP

<?php
try {
  例外が発生する可能性のある処理
} catch (例外クラス名 $変数名) {
  例外が発生した場合の処理
}
?>

下記の例では、tryブロック内の処理でdivision関数の第2引数を0にして呼び出した際に例外が発生し、Exceptionクラスのオブジェクトが投げられます。続いて、例外クラス名でExceptionを指定したcatchブロックで例外が捕捉され、処理が行われていることが確認できます。なお、tryブロック内で例外が発生しなかった場合は、catchブロックの処理は行われません。

PHP

<?php
function division($arg1, $arg2)
{
  if ($arg2 === 0) {
    throw new Exception('ゼロ除算はできません。');
  }
  $answer = $arg1 / $arg2;
  return $answer;
}

echo 'try catch start', PHP_EOL;

try {
  echo 'try start', PHP_EOL;
  echo division(10, 2), PHP_EOL;
  echo division(10, 0), PHP_EOL;
  echo 'try end', PHP_EOL;
} catch (Exception $e) {
  echo 'catch start', PHP_EOL;
  echo $e->getMessage(), PHP_EOL;
  echo 'catch end', PHP_EOL;
}

echo 'try catch end', PHP_EOL;
/* 出力:
try catch start
try start
5
catch start
ゼロ除算はできません。
catch end
try catch end
*/
?>

Exceptionクラス

Exceptionクラスは、PHPのすべての例外のスーパークラスです。Exceptionクラスに定義されたメソッドを通して、デバッグに役立つプロパティを利用することができます。また、Exceptionクラス以外にもStandard PHP Library(SPL)には、様々な例外が定義されているので、状況に応じて利用するとよいでしょう。

PHP

<?php
try {
  throw new Exception('例外メッセージ');
} catch (Exception $e) {
  // 例外メッセージ
  echo $e->getMessage(), PHP_EOL;
  // 例外コード
  echo $e->getCode(), PHP_EOL;
  // 例外が発生したファイル名
  echo $e->getFile(), PHP_EOL;
  // 例外が発生した行
  echo $e->getLine(), PHP_EOL;
  // スタックトレースの配列
  echo $e->getTrace(), PHP_EOL;
  // スタックトレースの文字列
  echo $e->getTraceAsString(), PHP_EOL;
  // 例外オブジェクトの文字列表現
  echo $e->__toString(), PHP_EOL;
}
?>

例外の拡張(Exceptionクラスの継承)

Exceptionクラスを継承して、独自の例外を定義することもできます。また、catchブロックは複数用意することができるため、発生した例外に応じて処理を分岐することも可能です。

PHP

<?php
class FooException extends Exception {}
class BarException extends Exception {}

try {
  throw new BarException();
} catch (FooException $e) {
  echo 'FooException', PHP_EOL;
} catch (BarException $e) {
  echo 'BarException', PHP_EOL;
} catch (Exception $e) {
  echo 'Exception', PHP_EOL;
}
// 出力:BarException
?>

例外のネスト

PHP5.3以降では、例外をネストさせることもできます。下記の例では、内側のtryで発生した例外をそのままthrowしています。外側のcatchでは、例外クラス名でExceptionを指定していますが、発生したFooExceptionを捕捉できていることがわかります。PHPでは、Exceptionはすべての例外のスーパークラスなので、Exceptionを継承して定義されたオブジェクトも捕捉できてしまいます。特にcatchブロックを複数用意する場合は注意が必要です。

PHP

<?php
class FooException extends Exception
{
  public function __construct($message, $code = 0, Exception $previous = null)
  {
    parent::__construct($message, $code, $previous);
  }
}

try {
  try {
    throw new FooException('FooException');
  } catch (FooException $e) {
    echo 'FooException', PHP_EOL;
    throw $e;
  }  
} catch (Exception $e) {
  echo 'Exception', PHP_EOL;
}
/* 出力:
FooException
Exception
*/
?>

set_exception_handler関数

set_exception_handler関数では、例外が捕捉されなかった場合のデフォルトの例外ハンドラを指定することができます。下記の例では、tryブロックの外で例外を投げているため、通常はFatal errorとなりますが、set_exception_handler()で例外ハンドラを指定しているため、exceptionHandler関数が呼ばれています。

注意すべき点として、例外ハンドラの処理後にPHPプログラムは実行を終了してしまいます。しかし、どこでどのような例外が発生したのかを知ることができるので、デバックを有利に進めることができるでしょう。

PHP

<?php
function exceptionHandler($e)
{
  echo $e->getMessage(), PHP_EOL;
}
set_exception_handler('exceptionHandler');
throw new Exception('catchされなかった例外');
echo 'end', PHP_EOL;
// 出力:catchされなかった例外
?>

エラーを例外に変換

PHPの組み込み関数の多くは、例外を投げるのではなくエラーを報告します。これは、PHP5まで例外がサポートされていなかったことに起因しています。しかし、set_error_handler()関数を利用することで、エラーを例外に変換することができます。下記の例では、発生したエラーをエラーハンドラでErrorExceptionオブジェクトとしてthrowしています。

PHP

<?php
function errorHandler($errno, $errstr, $errfile, $errline )
{
  throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('errorHandler');
try {
  $foo = array_reverse('foo');
} catch (ErrorException $e) {
  echo $e->getMessage(), PHP_EOL;
}
echo 'end', PHP_EOL;
/* 出力:
array_reverse() expects parameter 1 to be array, string given
end
*/
?>