例外とは?
状態:-
閲覧数:6,323
投稿日:2010-05-22
更新日:2014-03-03
・プログラムがある処理を実行している途中で、発生した何らかの異常
・正規の処理フローからは外れ、そのまま処理続行が出来ないような状況のこと
例
・0除算
・テキストファイルを読み込むのにそのテキストファイルが存在しない
・データベース処理においてSQL文が構文エラー、など
・プログラムがある処理を実行している途中で何らかの異常が発生した場合、現在の処理を中断(中止)して、別の処理を行うこと
・「実行中にエラーが発生した場合の処理」を予め記述しておき、実際にエラーが発生した場合でも、処理を中断することなく、予め記述しておいたその処理を実行すること(=エラー発生時の処理)
※PHPで例外処理を行う(予め記述しておいた“エラー発生時の処理”を実行する)には、発生したエラーを「例外」として“投げ”、”捕捉する"必要がある
例
・割り算の前に割る数が0ではないかをチェックし、0ならエラーメッセージを表示して処理中断
・テキストファイルの中身を読み込む処理の場合、テキストファイルが存在するか確認してなければ作成、など
例外処理を導入するメリット
・例外が起こっても、実行中のプログラムエラーを捕捉して、異常な動作をしないよう制御することができる
Fatal Error が発生した際、例外が投げられる時もあれば、投げられない時もある理由
・PHPの組み込み関数の多くは、例外を投げるのではなくエラーを報告する仕様になっているため
※エラー報告…設定により、「出力」or「ログ書き込み」の何れか、となる
PHPの組み込み関数の多くが、例外を投げるのではなくエラーを報告する仕様になっている理由
・PHP5まで例外がサポートされていなかったことに起因
・正規の処理フローからは外れ、そのまま処理続行が出来ないような状況のこと
例
・0除算
・テキストファイルを読み込むのにそのテキストファイルが存在しない
・データベース処理においてSQL文が構文エラー、など
例外処理とは?
・プログラムがある処理を実行している途中で何らかの異常が発生した場合、現在の処理を中断(中止)して、別の処理を行うこと
・「実行中にエラーが発生した場合の処理」を予め記述しておき、実際にエラーが発生した場合でも、処理を中断することなく、予め記述しておいたその処理を実行すること(=エラー発生時の処理)
※PHPで例外処理を行う(予め記述しておいた“エラー発生時の処理”を実行する)には、発生したエラーを「例外」として“投げ”、”捕捉する"必要がある
例
・割り算の前に割る数が0ではないかをチェックし、0ならエラーメッセージを表示して処理中断
・テキストファイルの中身を読み込む処理の場合、テキストファイルが存在するか確認してなければ作成、など
導入メリット
例外処理を導入するメリット
・例外が起こっても、実行中のプログラムエラーを捕捉して、異常な動作をしないよう制御することができる
例外仕様
Fatal Error が発生した際、例外が投げられる時もあれば、投げられない時もある理由
・PHPの組み込み関数の多くは、例外を投げるのではなくエラーを報告する仕様になっているため
※エラー報告…設定により、「出力」or「ログ書き込み」の何れか、となる
PHPの組み込み関数の多くが、例外を投げるのではなくエラーを報告する仕様になっている理由
・PHP5まで例外がサポートされていなかったことに起因
例外処理の構文
例外処理の構文
1.tryブロック内で何か例外が発生した場合(=例外クラスをthrowした場合)
・その時点でtryブロック内の処理が中断し、catchブロック内へ処理が進む
・catchブロック内の処理が終了後、以降の処理へ進む
2.tryブロック内で何も起こらなかった場合
・tryブロック内の処理が全て終了後、catchブロックは無視され、以降の処理へ進む
・投げられた例外を「キャッチ(つかまえる)」しているが、出力処理を記載していないため、何も表示されない例
try{throw new Exception('エラーが発生しました');
}catch (Exception $e) {
}
役割分担
try
・エラーが起こりそうな個所を「トライ(試す)」
throw
・スロー(投げる)
・トライ中にエラーが発生したら、例外を「スロウ(投げる)」
・明示的に例外を発生させること
・例外を投げるには、throwキーワードを使用し、Exceptionクラス(のインスタンス)またはExceptionクラス子クラスのオブジェクト(インスタンス)を投げる
・「Exceptionクラス子クラス」のインスタンスを、「例外」として投げる場合は、「catch」の引数に、投げられる「例外オブジェクト」のクラス名を指定する必要がある
catch
・投げられた例外を「キャッチ(つかまえる)」して、エラー時用処理を実行
Exceptionクラス
すべての例外のスーパークラス
・Exceptionクラスに定義されたメソッドを通して、デバッグに役立つプロパティを利用することができる
・また、Exceptionクラス以外にもStandard PHP Library(SPL)には、様々な例外が定義されている
・「Exception」クラスと無関係なクラスのインスタンスを投げたり、また投げられた「例外オブジェクト」のクラス名が指定されていないと、エラー
・Exceptionクラスに定義されたメソッドを通して、デバッグに役立つプロパティを利用することができる
・また、Exceptionクラス以外にもStandard PHP Library(SPL)には、様々な例外が定義されている
・「Exception」クラスと無関係なクラスのインスタンスを投げたり、また投げられた「例外オブジェクト」のクラス名が指定されていないと、エラー
コード例
投げない
例外を投げていない
・例外を「スロウ(投げる)」していないため、エラーが起こっても「トライ」が全部実行され「例外処理」も実行されない、無意味なサンプル
※「例外処理」は、「例外」が「スロウ(投げる)」されてはじめて実行される
echo "--- 例外 テスト ---\n\n";//①
try{
echo "→→→ try 開始 →→→\n\n";//②
#引数が不正な関数コール(「Warning」警告)
//substr();
echo "←←← try 終了 ←←←\n\n";
}catch(Exception $e){
echo "****** catch ******\n\n";
}
echo "--- 例外 テスト ---";
・結果
--- 例外 テスト ---
→→→ try 開始 →→→
←←← try 終了 ←←←
--- 例外 テスト ---
→→→ try 開始 →→→
←←← try 終了 ←←←
--- 例外 テスト ---
投げる
「例外」を「スロウ(投げる)」して「例外処理」を実行するサンプル
echo "--- 例外 テスト ---\n\n";//①
try{
echo "→→→ try 開始 →→→\n\n";//②
echo "- 例外が投げられました。-\n\n";
throw new Exception("例外が投げられました。");//③「try」ブロックの処理はこの一文が実行された時点で中断。「Exception」クラスのインスタンスを生成し、コンストラクタに「"例外が投げられました。"」という文字列を渡している
echo "←←← try 終了 ←←←\n\n";
}catch(Exception $e){//④投げられた「例外」を「キャッチ」。「変数$e」は必ず「Exception」クラスのインスタンスが渡されることを想定した「catch」の引数。つまり、「throw文」によって投げられた「Exception」クラスのオブジェクトは「変数$e」に引数として代入され、キャッチブロック内で利用されることになる。
echo "****** catch ******\n\n";
echo '$e->getMessage() : ', $e->getMessage(), "\n\n";//キャッチブロック内の「例外処理」を実行
//echo "\$e : \n", $e, "\n\n";
}
echo "--- 例外 テスト ---\n\n";//
・結果
--- 例外 テスト ---
→→→ try 開始 →→→
- 例外が投げられました。-
****** catch ******
$e->getMessage() : 例外が投げられました。
--- 例外 テスト ---
→→→ try 開始 →→→
- 例外が投げられました。-
****** catch ******
$e->getMessage() : 例外が投げられました。
--- 例外 テスト ---
投げる順番
最初の「throw文」が実行される前に、コンストラクタ内の「throw文」が実行される例
class NotException{}
class Ex1 extends Exception{
public function __construct($message = NULL, $code = 0){
parent::__construct($message, $code);
//throw new NotException; //「Not例外オブジェクト」を生成して投げようとしている//Fatal error: Exceptions must be valid objects derived from the Exception base class
}
}
try{
throw new Ex1;//①「例外オブジェクト」を生成して投げようとしている
}catch(Exception $e){
}
0除算
エラーを発生させ、try~catchで例外を補足している例
・tryブロック内の処理でdivision関数の第2引数を0にして呼び出した際に例外が発生し、Exceptionクラスのオブジェクトが投げられる。続いて、「例外クラス名でExceptionを指定したcatchブロック」で例外が捕捉され、処理が行われていることが確認できる。なお、tryブロック内で例外が発生しなかった場合は、catchブロックの処理は行われない
function division($arg1, $arg2)
{
if ($arg2 === 0) {
throw new Exception('ゼロ除算はできません。<br>');
}
$answer = $arg1 / $arg2;
return $answer;
}
echo 'try catch start<br>', PHP_EOL;//定数 ソース上で改行
try {
echo 'try start<br>', PHP_EOL;
echo division(10, 2)."<br>", PHP_EOL;
echo division(10, 0), PHP_EOL;
echo 'try end<br>', PHP_EOL;
} catch (Exception $e) {
echo 'catch start<br>', PHP_EOL;
echo $e->getMessage(), PHP_EOL;
echo "例外コード:" . $e->getCode()."<br>", PHP_EOL;
echo "行番号:" . $e->getLine()."<br>", PHP_EOL;
echo "<pre>バックトレース:<br>";
p( $e->getTrace( ) );
echo "</pre>";
echo 'catch end<br>', PHP_EOL;
}
echo 'try catch end<br>例外処理後、スクリプト継続', PHP_EOL;
/* 出力:
try catch start
try start
5
catch start
ゼロ除算はできません。
例外コード:0
行番号:19
バックトレース:
Array
(
[0] => Array
(
[file] => /○○/PHPStyleNote/basic/exception/7.php
[line] => 30
[function] => division
[args] => Array
(
[0] => 10
[1] => 0
)
)
)
catch end
try catch end
例外処理後、スクリプト継続
*/
簡易クラス
class Calc
{
public static function division($int1, $int2){
try {
if ($int2 == 0) {
throw new Exception('0で割ろうとしました');
}
$answer = $int1 / $int2;
echo $answer . "
";
echo '割り算が終了しました' . "
";
} catch (Exception $e) {
echo $e->getMessage() . "
";
echo '割り算が異常終了しました' . "
";
}
}
}
Calc::division(10, 2);
Calc::division(10, 0);
・結果
5
割り算が終了しました
0で割ろうとしました
割り算が異常終了しました
割り算が終了しました
0で割ろうとしました
割り算が異常終了しました
場合分け項目を増加
function division($num1, $num2){//②
try{
if($num2 === 0)//「比較演算子===」等しい(型も同じ) ⑩
throw new Exception('「0」で割ることは出来ません。');
else if(!is_numeric($num1) || !is_numeric($num2))//「論理和||」いずれかのオペランドが「真」なら「true」 を返す
throw new Exception('値が不正です。');//⑥
return $num1 / $num2;//③ ⑧0÷10=0 「0を割る」は成立する。
}catch(Exception $e){
return $e->getMessage();
}
}
echo division(10, 2), "\n";//①④
echo division(10, ""), "\n";//⑤
echo division(0, 10), "\n";//⑦
echo division(10, 0), "\n";//⑨
・結果
5
値が不正です。
0
「0」で割ることは出来ません。
値が不正です。
0
「0」で割ることは出来ません。
例外ごとのクラス作成
class DivisionEx extends Exception{}//Exception」クラスを継承した「DivisionException」クラスを定義。“割算における「例外」”
class DivisionIsZeroEx extends DivisionEx{//「DivisionEx」クラスを継承した「DivisionIsZeroEx」クラスを定義。“分母が「0」であるという「例外」”
protected $message = '「0」で割ることは出来ません。';
}
class DivisionWrongValueEx extends DivisionEx{//「DivisionEx」クラスを継承した「DivisionWrongValueEx」クラスを定義。“値が不正であるという「例外」”
protected $message = '値が不正です。';//⑮
}
class Division{//割算を行うためのクラス。コンストラクタに渡された2つの値をチェックし、問題があれば「例外」を投げ、問題がなければ(「例外」が投げられていなければ)演算結果をプロパティに保存
private $result;//⑤⑳
public function __construct($num1, $num2){
if($num2 === 0)
throw new DivisionIsZeroEx;
else if(!is_numeric($num1) || !is_numeric($num2))
throw new DivisionWrongValueEx;//⑬
$this->result = $num1 / $num2;//④⑲
}
public function getResult(){//⑦
return $this->result;
}
}
function division($num1, $num2){//②⑪⑰
try{//投げる元はココ
$division = new Division($num1, $num2);//③⑫⑱
return $division->getResult();//⑥⑧
}catch(DivisionEx $e){//ここでキャッチ
return $e->getMessage();//⑭
}
}
echo division(10, 2), "\n";//①⑨
echo division(10, ""), "\n";//⑩
echo division(0, 10), "\n";//⑯
echo division(10, 0), "\n";
・結果
5
値が不正です。
0
「0」で割ることは出来ません。
値が不正です。
0
「0」で割ることは出来ません。
ファイルオープン
処理基点となるスクリプトをtry~catchすることで、全ての例外をそこで捕まえる例
class File
{
public static function read($path)
{
if (false == file_exists($path)) {
throw new Exception('ファイルが存在しません');
}
$fp = @fopen($path, 'r');
if (false === $fp) {
throw new Exception('ファイルのオープンに失敗しました');
}
$data = @stream_get_contents($fp);
if (false === $fp) {
throw new Exception('ファイルの読込に失敗しました');
}
return $data;
}
}
class StringViewer
{
public function display($path)
{
try {
$data = File::read($path);
echo $data;
} catch (Exception $e) {
die($e->getMessage());
}
}
}
$viewer = new StringViewer();
$viewer->display('C:/aaa.txt');
・結果
ファイルが存在しません
コード例 … Exceptionクラス
Exceptionクラス
「Exception」クラス仕様をデバッグ確認
・可視性の関係で、「Exceptionクラス」の派生クラス「MyExceptionクラス」作成
class MyException extends Exception{
function __construct($message = NULL, $code = 0){//②
parent::__construct($message, $code);
$arr["class_vars"] = get_class_vars("Exception");
$arr["class_methods"] = get_class_methods("Exception");
// $arr["object_vars"] = get_object_vars($this);
print_r($arr);
// var_dump($this);
}
}
new MyException("例外を投げまーす。");//①
・結果
Array
(
[class_vars] => Array
(
[message] =>
[code] => 0
[file] =>
[line] =>
[class_methods] => Array
(
[0] => __construct
[1] => getMessage
[2] => getCode
[3] => getFile
[4] => getLine
[5] => getTrace
[6] => getPrevious
[7] => getTraceAsString
[8] => __toString
)
Exceptionクラスメソッド
・例外クラスのメソッド
class Ex1 extends Exception{
public function __construct($mes, $code){//⑥
throw new Exception($mes, $code);//⑦一番最初に実行される(最後に呼ばれた)「例外」が、投げられた位置はココ!
}
}
class AIR{
public function __construct($mes){
$this->air_method($mes);//③function「air_method」呼び出す
}
private function air_method($mes){//④
try{
throw new Ex1($mes, 12345);//⑤function「__construct」呼び出す
}catch(Exception $e){
echo '$e->getMessage();', "\n", $e->getMessage(),"\n\n";//⑧「getMessage()」メソッド -「プロパティ$message」の値 =「例外オブジェクト」生成時に渡されたメッセージ(文字列)
echo '$e->getCode();', "\n", $e->getCode(), "\n\n";//⑨「getCode()」メソッド 「プロパティ$code」の値 = 「例外オブジェクト」生成時に渡された識別コード(整数)
//echo '$e->getFile();', "\n", $e->getFile(), "\n\n";//(省略)/public_html/php.w4c.work/0-PROTOTYPE/exception9_exceptin_method.html 「getFile()」メソッド -「プロパティ$file」の値 =「例外」が投げられたファイルへの絶対パス
echo '$e->getLine();', "\n", $e->getLine(), "\n\n";//「getLine()」メソッド -「プロパティ$line」の値 =「例外」が投げられた位置(行番号)
echo '$e->getTrace();', "\n";
print_r( $e->getTrace() );//「getTrace()」メソッド -「プロパティ$trace」の値(配列)=「例外」が投げられるまでの足跡(昔に逆戻る)を配列で格納
// echo "\n", '$e->getTraceAsString();', "\n",$e->getTraceAsString();
// echo "\n\n", '__toString()', "\n", $e;//__toString()」メソッド -「例外オブジェクト」を文字列として評価した際、「getTraceAsString()」メソッドと似た値を返す = 「Exception」クラス内部で使用する値なので、理解する必要なし
}
}
}
function air_func(){
new AIR(" AIRクラスのインスタンスオブジェクト生成と同時に、コンストラクタが起動される ");//②function「__construct」呼び出す
}
air_func();//①function「air_func」呼び出す
・結果
$e->getMessage();
AIRクラスのインスタンスオブジェクト生成と同時に、コンストラクタが起動される
$e->getCode();
12345
$e->getLine();
15
$e->getTrace();
Array
(
[0] => Array
(
[file] => /var/www/php.w4c.work/public_html/0-PROTOTYPE/exception9_exceptin_method.html
[line] => 25
[function] => __construct
[class] => Ex1
[type] => ->
[args] => Array
(
[0] => AIRクラスのインスタンスオブジェクト生成と同時に、コンストラクタが起動される
[1] => 12345
[1] => Array
(
[file] => /var/www/php.w4c.work/public_html/0-PROTOTYPE/exception9_exceptin_method.html
[line] => 21
[function] => air_method
[class] => AIR
[type] => ->
[args] => Array
(
[0] => AIRクラスのインスタンスオブジェクト生成と同時に、コンストラクタが起動される
[2] => Array
(
[file] => /var/www/php.w4c.work/public_html/0-PROTOTYPE/exception9_exceptin_method.html
[line] => 40
[function] => __construct
[class] => AIR
[type] => ->
[args] => Array
(
[0] => AIRクラスのインスタンスオブジェクト生成と同時に、コンストラクタが起動される
[3] => Array
(
[file] => /var/www/php.w4c.work/public_html/0-PROTOTYPE/exception9_exceptin_method.html
[line] => 44
[function] => air_func
[args] => Array
(
)
コード例 … 「try catch」複数
「try catch」複数
「try」後の「catch」は複数設置可能
・最初の「catch」から順に、「投げられた例外オブジェクトのクラス名」と「引数に指定したクラス名」がマッチするかチェックしていく
「投げたクラス」もしくは「親クラス」はキャッチ可能
・最初にマッチした所の「キャッチ」ブロック処理が実行される
・catch記述の順番次第で、どこでcatchするかが変わってくる
class Ex1 extends Exception{}
class Ex2 extends Exception{}
class Ex3 extends Ex1{}
class Ex4 extends Ex2{}
class NotEx{}
for($i = 0; $i < 6; $i++){
try{
switch($i){
case 0://①
echo "「親クラスException」の例外が投げられる<br>";
throw new Exception;
case 1://③
echo "「子クラスEx1」の例外が投げられる<br>";
throw new Ex1;
case 2://⑤
echo "「子クラスEx2」の例外が投げられる<br>";
throw new Ex2;
case 3://⑦
echo "「孫クラスEx3」の例外が投げられる<br>";
throw new Ex3;
case 4://⑨
echo "「孫クラスEx4」の例外が投げられる<br>";
throw new Ex4;
//case 5://⑪
// echo "- NotEx 例外が投げられました。<br>";
// throw new NotEx;
}
//※catch記述の順番次第で、どこでcatchするかは変わる
}catch(Ex1 $e){//④同クラスでキャッチ ⑧親(親)クラスでキャッチ
echo "Ex1で、例外をキャッチ<br>";
echo get_class($e), " was caught.<br><br>";
}catch(Exception $e){//②同クラスでキャッチ ⑥親(親)クラスでキャッチ ⑩親の親(親)クラスでキャッチ
echo "「親クラスException」で、例外をキャッチ<br>";
echo get_class($e), " was caught.<br><br>";
}catch(Ex2 $e){
echo "Ex2で、例外をキャッチ<br>";
echo get_class($e), " was caught.<br><br>";
// }catch(NotEx $e){//「Notクラス」は、「Exceptionクラス」と無関係なため、「例外」とみなされずに「ExFatal error: Exceptions must be valid objects derived from the Exception base class」となる。
// echo "NotExで、例外をキャッチ<br>";
// echo get_class($e), " was caught.<br><br>";
}
}
・結果
「親クラスException」の例外が投げられる
「親クラスException」で、例外をキャッチ
Exception was caught.
「子クラスEx1」の例外が投げられる
Ex1で、例外をキャッチ
Ex1 was caught.
「子クラスEx2」の例外が投げられる
「親クラスException」で、例外をキャッチ
Ex2 was caught.
「孫クラスEx3」の例外が投げられる
Ex1で、例外をキャッチ
Ex3 was caught.
「孫クラスEx4」の例外が投げられる
「親クラスException」で、例外をキャッチ
Ex4 was caught.
「親クラスException」で、例外をキャッチ
Exception was caught.
「子クラスEx1」の例外が投げられる
Ex1で、例外をキャッチ
Ex1 was caught.
「子クラスEx2」の例外が投げられる
「親クラスException」で、例外をキャッチ
Ex2 was caught.
「孫クラスEx3」の例外が投げられる
Ex1で、例外をキャッチ
Ex3 was caught.
「孫クラスEx4」の例外が投げられる
「親クラスException」で、例外をキャッチ
Ex4 was caught.
コード例 … 「例外処理」はネストすることができる
ネスト
PHP5.3以降では、例外をネストさせることができる
・「例外」を投げたレベルでキャッチされなかった場合には、キャッチされるまで、上位レベルを探し続けていく
class Ex1 extends Exception{}
class Ex2 extends Exception{}
try{//①
try{//②
try{//③
echo "Ex1 例外を投げる \n";//④
throw new Ex1;//同レベルでは捕捉できない
}catch(Ex2 $e){} //上位レベルを探していく
}catch(Exception $e){//⑤ここで捕捉
echo get_class($e), " 例外をキャッチ \n\n";
echo "Ex2 例外を投げる \n";
throw new Ex2;
}
}catch(Exception $e){
echo get_class($e), " 例外をキャッチ \n\n";
echo "ここで更に Ex2 例外を投げると、既に最上位レベルなのでキャッチされず、エラーとなる \n";
//throw new Ex2;//Fatal error: Uncaught exception 'Ex2'。
}
・結果
Ex1 例外を投げる
Ex1 例外をキャッチ
Ex2 例外を投げる
Ex2 例外をキャッチ
ここで更に Ex2 例外を投げると、既に最上位レベルなのでキャッチされず、エラーとなる
Ex1 例外をキャッチ
Ex2 例外を投げる
Ex2 例外をキャッチ
ここで更に Ex2 例外を投げると、既に最上位レベルなのでキャッチされず、エラーとなる
・例外クラス名でExceptionを指定した箇所で、「Exceptionを継承して定義されたオブジェクト」を捕捉
class FooException extends Exception//Exceptionクラスを継承
{
public function __construct($message, $code = 0, Exception $previous = null)//2.コンストラクタ実行
{
parent::__construct($message, $code, $previous);//3.親クラスコンストラクタ実行
}
}
try {
try {
throw new FooException('FooException');//1.「Exceptionクラスを継承したFooExceptionクラス」のインスタンスオブジェクトを生成
} catch (FooException $e) {//4.FooExceptionクラスの例外を補足
echo 'FooException', PHP_EOL;//5.
throw $e;//6.発生したFooExceptionクラスの例外をのままthrow
}
} catch (Exception $e) {//7.例外クラス名でExceptionを指定しているのに、発生したFooExceptionを捕捉できている。PHPでは、Exceptionはすべての例外のスーパークラスなので、Exceptionを継承して定義されたオブジェクトも捕捉できるため
echo 'Exception', PHP_EOL;
}
// 出力:
//FooException
//Exception
例外 × 出力バッファリング関数
try~catchを利用した最も簡単な例
・結果
try
{
ob_start();
echo "バッファした内容!";
throw new Exception();
}
catch ( Exception $e )
{
$content = ob_get_clean();
}
echo "こっちが先。";
echo $content;
・結果
こっちが先。バッファした内容!