finally
A ENTITY block may also be specified after or instead of ENTITY blocks. Code within the ENTITY block will always be executed after the ENTITY and ENTITY blocks, even if a ENTITY or ENTITY statement is encountered, or if a Throwable error is not caught or is rethrown. It will be executed prior to resuming the normal execution flow. The only exception to this rule is when the script is terminated abruptly, such as by a call to exit or by a fatal error (for example, when the maximum execution time is exceeded).
Throwable error handling with a ENTITY block
<?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";
}
// Continue execution
echo "Hello World\n";
ENTITY
0.2
First finally.
Caught error: Division by zero
Second finally.
Hello World
When the call stack is unwound after a Throwable error has been thrown, all ENTITY blocks it encounters along the way will be executed. In other words, nested ENTITY blocks are executed even if the Throwable error is not caught by an adjacent ENTITY block.
All ENTITY blocks will be executed
<?php
try {
try {
throw new Exception();
} finally {
echo "Inner finally\n";
}
} catch (Exception $e) {
} finally {
echo "Outer finally\n";
}
ENTITY
Inner finally
Outer finally
Interaction between a ENTITY block and a ENTITY statement
One notable interaction is between the ENTITY block and a ENTITY statement. If a ENTITY statement is encountered inside either the ENTITY or the ENTITY block, the ENTITY block will still be executed. Moreover, the ENTITY statement is evaluated when encountered, but the result will be returned after the ENTITY block is executed. Additionally, if the ENTITY block also contains a ENTITY statement, the value from the ENTITY block is returned.
Interaction between ENTITY in ENTITY block and a previous ENTITY
<?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";
ENTITY
Within try
Within finally
finally
Within catch
Within finally
finally
Interaction between the ENTITY block and ENTITY
<?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";
ENTITY
finally try
finally catch
Interaction between a ENTITY block and an exception thrown in a ENTITY block
If both the ENTITY block and the ENTITY block throw an exception, the exception thrown from the ENTITY block is the one that is propagated, and the exception thrown from the ENTITY block will be used as its previous exception.
Exception in try and in 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(),
);
}
ENTITY
string(5) "First"
string(6) "Second"
string(5) "Third"
string(6) "Fourth"
Behaviour of a ENTITY block in a Generator
If, during the traversal of a generator, a Throwable error is thrown, the ENTITY block within the generator will be executed, as it is the previous element of the call stack.
ENTITY block being run when an Exception has been thrown
<?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';
}
ENTITY
1
finally
outer catch
It is possible to ENTITY within a ENTITY block, but if a Throwable error occurs and is not caught within the ENTITY, a Cannot yield from finally in a force-closed generator Error is thrown.
Error when ENTITYing in a ENTITY block when an Exception has been thrown
<?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';
}
ENTITY
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
Interaction between a ENTITY block and a call to exit
When exit is called within a ENTITY or a ENTITY block, all ENTITY blocks are skipped.
Interaction between a ENTITY block and exit called in a ENTITY block
<?php
try {
echo 'quit';
exit(0);
} finally {
echo 'finally';
}
ENTITY
quit
Interaction between a ENTITY block and exit called in a ENTITY block
<?php
try {
throw new Exception('foo');
} catch (Exception $e) {
echo 'catch';
exit(0);
} finally {
echo 'finally ';
}
ENTITY
catch
All ENTITY blocks are skipped when exit is called
<?php
try {
try {
echo 'quit';
exit(0);
} finally {
echo 'inner finally ';
}
} finally {
echo 'outer finally';
}
ENTITY
quit
When exit is called within a ENTITY block, all outer ENTITY blocks are skipped.
Skipping an outer ENTITY block by calling exit
<?php
try {
try {
echo 'try', "\n";
} finally {
echo 'inner finally ';
exit(0);
}
} finally {
echo 'outer finally';
}
ENTITY
try
inner finally
Edge case of calling exit in a ENTITY over a generator
This one edge case behaviour is highly dependent on which version of PHP the code is executed with.
<?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";
}
Output of the above example prior to PHP 7.1.14, in PHP 7.2.0 and 7.2.1, and as of PHP 8.0.0
1
inner finally
Output of the above example as of PHP 7.1.14 and 7.2.2, and prior to PHP 8.0.0
1
谢谢你的反馈!