« Try...Catch
関数 »

finally

(No version information available, might only be in Git)

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

例4 finally ブロックと return の相互作用

<?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
関連キーワード:
finally   ブロック   catch   Exception   return   exit   yield   throw   相互作用   Within   generate   Throwable   getMessage   getPrevious   outer   foreach   inner   First   Second   testReturnTry   testReturnCatch   ジェネレータ   GbFmu   スキップ   quit
関連する関数・ページ:
control-structures.try-catch   language.error-handling.throwable   generator.throw   exception.getprevious   exception.getmessage   generator.rewind