(PHP 5 >= 5.5.0, PHP 7, PHP 8)
catch ブロックの後、またはその代わりに、
finally ブロックも指定できます。
finally ブロックの中に書いたコードは、
return 文や yield 文に到達した場合や、
Throwable エラーがキャッチされなかったり、
スローしなおされたりした場合でも、
try および catch ブロックの後で常に実行されます。
このコードは、通常の実行フローが再開される前に実行されます。
このルールが当てはまらないのは、exit() の呼び出しや、
致命的なエラー (最大実行時間を超えた場合など) によって、
スクリプトが突然終了した場合だけです。
例1 finally ブロックを使った Throwable エラーの処理
<?php
function inverse($x) {
return 1/$x;
}
try {
echo inverse(5) . "\n";
} catch (\DivisionByZeroError $e) {
echo 'Caught error: ', $e->getMessage(), "\n";
} finally {
echo "First finally.\n";
}
try {
echo inverse(0) . "\n";
} catch (\DivisionByZeroError $e) {
echo 'Caught error: ', $e->getMessage(), "\n";
} finally {
echo "Second finally.\n";
}
// 実行は継続される
echo "Hello World\n";上の例の PHP 8 での出力は、このようになります。:
0.2 First finally. Caught error: Division by zero Second finally. Hello World
Throwable エラーがスローされた後にコールスタックが巻き戻されるときは、
その途中で見つかったすべての finally ブロックが実行されます。
つまり、ネストした finally ブロックは、
隣接する catch ブロックで Throwable
エラーがキャッチされなかった場合でも実行されます。
例2 すべての finally ブロックが実行される
<?php
try {
try {
throw new Exception();
} finally {
echo "Inner finally\n";
}
} catch (Exception $e) {
} finally {
echo "Outer finally\n";
}上の例の出力は以下となります。
Inner finally Outer finally
finally ブロックと return 文の相互作用
finally ブロックと return 文の間には注意すべき相互作用があります。
try や catch ブロックの内部で return 文に到達した場合でも、
finally ブロックは実行されます。
さらに、return 文は到達した時点で評価されますが、
結果は finally ブロックが実行された後に返されます。
また、finally ブロックにも return 文が存在した場合は、
finally ブロックから値が返されます。
例3 finally ブロック内の return と、それより前の return の相互作用
<?php
function testReturnTry() {
try {
echo 'Within try', "\n";
return 'try';
} finally {
echo 'Within finally', "\n";
return 'finally';
}
}
function testReturnCatch() {
try {
throw new Exception('foo');
} catch (Exception $e) {
echo 'Within catch', "\n";
return 'catch';
} finally {
echo 'Within finally', "\n";
return 'finally';
}
}
echo testReturnTry(), "\n";
echo testReturnCatch(), "\n";上の例の出力は以下となります。
Within try Within finally finally Within catch Within finally finally
<?php
function testReturnTry() {
try {
return 'try';
} finally {
echo 'finally ';
}
}
function testReturnCatch() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
echo 'finally ';
}
}
echo testReturnTry(), "\n";
echo testReturnCatch(), "\n";上の例の出力は以下となります。
finally try finally catch
finally ブロックと、try ブロック内でスローされた例外の相互作用
try ブロックと finally ブロックの両方から例外がスローされると、
finally ブロックからスローされた例外が伝播し、
try ブロックからスローされた例外はその前の例外として使われます。
例5 try と finally の内部からスローされる例外
<?php
try {
try {
throw new Exception(message: 'Third', previous: new Exception('Fourth'));
} finally {
throw new Exception(message: 'First', previous: new Exception('Second'));
}
} catch (Exception $e) {
var_dump(
$e->getMessage(),
$e->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getPrevious()->getMessage(),
);
}上の例の出力は以下となります。
string(5) "First" string(6) "Second" string(5) "Third" string(6) "Fourth"
finally ブロックの振る舞い
ジェネレータを反復処理している途中で Throwable
エラーがスローされると、ジェネレータ内の finally ブロックが実行されます。
これは、コールスタック上のひとつ前の要素にあたるためです。
例6
Exception がスローされたときに実行される
finally ブロック
<?php
function generate()
{
try {
yield 1;
yield 2;
} catch (\Exception $e) {
echo 'catch';
} finally {
echo "finally\n";
}
}
try {
foreach (generate() as $i) {
echo $i, "\n";
throw new Exception();
}
} catch (Exception $e) {
echo 'outer catch';
}上の例の出力は、 たとえば以下のようになります。
1 finally outer catch
finally ブロックの中で yield を使うこともできますが、
Throwable エラーが発生して
foreach の中でキャッチされなかった場合は、Cannot yield from finally in a force-closed
generator というメッセージの Error がスローされます。
例7
Exception がスローされたときに
finally ブロック内で yield した場合のエラー
<?php
function generate()
{
try {
yield 1;
yield 2;
} catch (\Exception $e) {
echo 'catch';
} finally {
echo "finally\n";
yield 3;
}
}
try {
foreach (generate() as $i) {
echo $i, "\n";
throw new Exception();
}
} catch (Exception $e) {
echo 'outer catch';
}上の例の出力は、 たとえば以下のようになります。
1
finally
Fatal error: Uncaught Exception in /in/GbFmu:19
Stack trace:
#0 {main}
Next Error: Cannot yield from finally in a force-closed generator in /in/GbFmu:12
Stack trace:
#0 /in/GbFmu(19): generate()
#1 {main}
thrown in /in/GbFmu on line 12
finally ブロックと exit() の呼び出しの相互作用
try ブロックや catch ブロックの中で exit() を呼び出すと、
すべての finally ブロックがスキップされます。
例8 finally ブロックと、try ブロック内で呼び出された exit() の相互作用
<?php
try {
echo 'quit';
exit(0);
} finally {
echo 'finally';
}上の例の出力は以下となります。
quit
例9 finally ブロックと、catch ブロック内で呼び出された exit() の相互作用
<?php
try {
throw new Exception('foo');
} catch (Exception $e) {
echo 'catch';
exit(0);
} finally {
echo 'finally ';
}上の例の出力は以下となります。
catch
例10 exit() を呼び出すとすべての finally ブロックがスキップされる
<?php
try {
try {
echo 'quit';
exit(0);
} finally {
echo 'inner finally ';
}
} finally {
echo 'outer finally';
}上の例の出力は以下となります。
quit
finally ブロックの中で exit() を呼び出すと、
その外側にある finally ブロックはすべてスキップされます。
例11 exit() を呼び出して外側の finally ブロックをスキップする
<?php
try {
try {
echo 'try', "\n";
} finally {
echo 'inner finally ';
exit(0);
}
} finally {
echo 'outer finally';
}上の例の出力は以下となります。
try inner finally
foreach の中で exit() を呼び出す場合のエッジケースこのエッジケースでの振る舞いは、コードを実行する PHP のバージョンによって大きく異なります。
<?php
function generate()
{
try {
yield 1;
yield 2;
} catch (\Exception $e) {
echo 'catch';
} finally {
echo "inner finally\n";
}
}
try {
foreach (generate() as $i) {
echo $i, "\n";
exit(0);
}
} finally {
echo "outer finally\n";
}PHP 7.1.14 より前、PHP 7.2.0 と 7.2.1、および PHP 8.0.0 以降では、 上の例の出力は以下となります。
1 inner finally
PHP 7.1.14 および 7.2.2 以降、かつ PHP 8.0.0 より前では、 上の例の出力は以下となります。
1