Hi all,
I'd like to gauge interest in deprecating return inside a finally
block, before I request karma to propose it as a full RFC.
A return inside a finally silently discards whatever the try/catch
was doing...a pending exception or return value just disappears, with no
error or notice.
As far as I know, it's the only abrupt exit from a finally PHP leaves
silent...
break/continue/goto out of one are already compile errors, and a
throw auto-chains the discarded exception as $previous. Only return
destroys the in-flight state and says nothing.
Looking into the history of finally I found that the author of the
original RFC said he added it only because Java allowed it, even though he
thought it made "no sense" https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).
I also found that it was the source of a few bugs:
- https://bugs.php.net/bug.php?id=70228
- https://bugs.php.net/bug.php?id=72213
- https://github.com/php/php-src/issues/11028
To see what a change would actually cost, I implemented the deprecation
https://github.com/php/php-src/compare/master...aldemeery:php-src:deprecate-return-in-finally
and ran it over the top ~5000 Composer packages. And I found that it's
rare...there were only 12 occurrences across 9 packages out of 4992. Of
those, 3 look like genuine latent bugs the deprecation caught, and the rest
look deliberate.
Here's the list of the occurrences:
- ibexa/admin-ui (swallows the exceptions its own docblock declares)
https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834 - shopware/core (eats thumbnail failures and still reports success)
https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294 - amphp/http-server (drops exceptions when the stream is already gone)
https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322 - dvdoug/PHPCoord (deliberate...finally is the method's return)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131 - dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148 - dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221 - dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247 - spatie/ray (delibrate...any failure just returns false)
https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71 - cakephp/cakephp (catch consumed the exception...finally just returns
array)
https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755 - hyperf/http-server (catch consumed the exception...finally emits
response)
https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140 - silverstripe/framework (catch consumed the mysqli error)
https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213 - symfony/flex (try can't actually throw)
https://github.com/symfony/flex/blob/4a6d98e/src/PackageResolver.php#L88
My take here is that the language already handles the analogous cases
(continue/break/goto/...etc) natively, so leaving return looks like
an inconsistency.
I lean toward deprecating it now with an eye to an error in a future major
(following the steps of Tim's deprecate-return-from-constructor RFC
https://wiki.php.net/rfc/deprecate-return-value-from-construct)...though
I guess a plain warning would be fine too. The main thing is that it seems
worth doing something about.
Is there appetite for this?
Thanks,
Osama
Hi all,
I'd like to gauge interest in deprecating
returninside afinallyblock, before I request karma to propose it as a full RFC.A
returninside afinallysilently discards whatever thetry/catchwas doing...a pending exception or return value just disappears, with no error or notice.As far as I know, it's the only abrupt exit from a
finallyPHP leaves silent...
break/continue/gotoout of one are already compile errors, and athrowauto-chains the discarded exception as$previous. Onlyreturndestroys the in-flight state and says nothing.Looking into the history of
finallyI found that the author of the original RFC said he added it only because Java allowed it, even though he thought it made "no sense" https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).I also found that it was the source of a few bugs:
- https://bugs.php.net/bug.php?id=70228
- https://bugs.php.net/bug.php?id=72213
- https://github.com/php/php-src/issues/11028
To see what a change would actually cost, I implemented the deprecation and ran it over the top ~5000 Composer packages. And I found that it's rare...there were only 12 occurrences across 9 packages out of 4992. Of those, 3 look like genuine latent bugs the deprecation caught, and the rest look deliberate.
Here's the list of the occurrences:
- ibexa/admin-ui (swallows the exceptions its own docblock declares)
- shopware/core (eats thumbnail failures and still reports success)
- amphp/http-server (drops exceptions when the stream is already gone)
- dvdoug/PHPCoord (deliberate...finally is the method's return)
- dvdoug/PHPCoord (again)
- dvdoug/PHPCoord (again)
- dvdoug/PHPCoord (again)
- spatie/ray (delibrate...any failure just returns false)
- cakephp/cakephp (catch consumed the exception...finally just returns array)
- hyperf/http-server (catch consumed the exception...finally emits response)
- silverstripe/framework (catch consumed the mysqli error)
- symfony/flex (try can't actually throw)
My take here is that the language already handles the analogous cases (
continue/break/goto/...etc) natively, so leavingreturnlooks like an inconsistency.I lean toward deprecating it now with an eye to an error in a future major (following the steps of Tim's deprecate-return-from-constructor RFC)...though I guess a plain warning would be fine too. The main thing is that it seems worth doing something about.
Is there appetite for this?
Thanks,
Osama
I would be in favour of this, it should be noted that C# does not permit "exiting" a finally block via control flow statements: [1]
It is a compile-time error for a break, continue, or goto statement to transfer control out of a finally block. When a break, continue, or goto statement occurs in a finally block, the target of the statement shall be within the same finally block, or otherwise a compile-time error occurs.
It is a compile-time error for a return statement to occur in a finally block.
Best regards,
Gina P. Banyard
[1] https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#1311-the-try-statement
Hi Gina, an outright prohibition is essentially the end state I lean toward
too.
I think it's the more consistent fit for PHP...break, continue, and
goto out of a finally are already errors here, the same as C#, so
making return an error as well would treat them all the same.
Regards,
Osama Aldemeery
On Thursday, 18 June 2026 at 23:01, Osama Aldemeery aldemeery@gmail.com
wrote:Hi all,
I'd like to gauge interest in deprecating
returninside afinally
block, before I request karma to propose it as a full RFC.A
returninside afinallysilently discards whatever thetry/catch
was doing...a pending exception or return value just disappears, with no
error or notice.As far as I know, it's the only abrupt exit from a
finallyPHP leaves
silent...
break/continue/gotoout of one are already compile errors, and a
throwauto-chains the discarded exception as$previous. Onlyreturn
destroys the in-flight state and says nothing.Looking into the history of
finallyI found that the author of the
original RFC said he added it only because Java allowed it, even though he
thought it made "no sense" https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).I also found that it was the source of a few bugs:
- https://bugs.php.net/bug.php?id=70228
- https://bugs.php.net/bug.php?id=72213
- https://github.com/php/php-src/issues/11028
To see what a change would actually cost, I implemented the deprecation
https://github.com/php/php-src/compare/master...aldemeery:php-src:deprecate-return-in-finally
and ran it over the top ~5000 Composer packages. And I found that it's
rare...there were only 12 occurrences across 9 packages out of 4992. Of
those, 3 look like genuine latent bugs the deprecation caught, and the rest
look deliberate.Here's the list of the occurrences:
- ibexa/admin-ui (swallows the exceptions its own docblock declares)
https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834- shopware/core (eats thumbnail failures and still reports success)
https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294- amphp/http-server (drops exceptions when the stream is already gone)
https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322- dvdoug/PHPCoord (deliberate...finally is the method's return)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247- spatie/ray (delibrate...any failure just returns false)
https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71- cakephp/cakephp (catch consumed the exception...finally just
returns array)
https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755- hyperf/http-server (catch consumed the exception...finally emits
response)
https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140- silverstripe/framework (catch consumed the mysqli error)
https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213- symfony/flex (try can't actually throw)
https://github.com/symfony/flex/blob/4a6d98e/src/PackageResolver.php#L88My take here is that the language already handles the analogous cases
(continue/break/goto/...etc) natively, so leavingreturnlooks like
an inconsistency.I lean toward deprecating it now with an eye to an error in a future major
(following the steps of Tim's deprecate-return-from-constructor RFC
https://wiki.php.net/rfc/deprecate-return-value-from-construct)...though
I guess a plain warning would be fine too. The main thing is that it seems
worth doing something about.Is there appetite for this?
Thanks,
OsamaI would be in favour of this, it should be noted that C# does not permit
"exiting" a finally block via control flow statements: [1]It is a compile-time error for a break, continue, or goto statement to
transfer control out of a finally block. When a break, continue, or goto
statement occurs in a finally block, the target of the statement shall be
within the same finally block, or otherwise a compile-time error occurs.It is a compile-time error for a return statement to occur in a finally
block.Best regards,
Gina P. Banyard
Hi all,
I'd like to gauge interest in deprecating
returninside afinally
block, before I request karma to propose it as a full RFC.A
returninside afinallysilently discards whatever the
try/catchwas doing...a pending exception or return value just
disappears, with no error or notice.As far as I know, it's the only abrupt exit from a
finallyPHP leaves
silent...
break/continue/gotoout of one are already compile errors, and a
throwauto-chains the discarded exception as$previous. Only
returndestroys the in-flight state and says nothing.Looking into the history of
finallyI found that the author of the
original RFC said he added it only because Java allowed it, even though
he thought it made "no sense" https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).I also found that it was the source of a few bugs:
• https://bugs.php.net/bug.php?id=70228
• https://bugs.php.net/bug.php?id=72213
• https://github.com/php/php-src/issues/11028
To see what a change would actually cost, I implemented the deprecation
https://github.com/php/php-src/compare/master...aldemeery:php-src:deprecate-return-in-finally
and ran it over the top ~5000 Composer packages. And I found that it's
rare...there were only 12 occurrences across 9 packages out of 4992. Of
those, 3 look like genuine latent bugs the deprecation caught, and the
rest look deliberate.Here's the list of the occurrences:
- ibexa/admin-ui (swallows the exceptions its own docblock declares)
https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834- shopware/core (eats thumbnail failures and still reports success)
https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294- amphp/http-server (drops exceptions when the stream is already
gone)
https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322- dvdoug/PHPCoord (deliberate...finally is the method's return)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247- spatie/ray (delibrate...any failure just returns false)
https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71- cakephp/cakephp (catch consumed the exception...finally just
returns array)
https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755- hyperf/http-server (catch consumed the exception...finally emits
response)
https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140- silverstripe/framework (catch consumed the mysqli error)
https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213- symfony/flex (try can't actually throw)
https://github.com/symfony/flex/blob/4a6d98e/src/PackageResolver.php#L88
My take here is that the language already handles the analogous cases
(continue/break/goto/...etc) natively, so leavingreturnlooks
like an inconsistency.I lean toward deprecating it now with an eye to an error in a future
major (following the steps of Tim's deprecate-return-from-constructor
RFC
https://wiki.php.net/rfc/deprecate-return-value-from-construct)...though
I guess a plain warning would be fine too. The main thing is that it
seems worth doing something about.Is there appetite for this?
Thanks,
Osama
I think this makes sense, but it would definitely need to go through a Deprecation phase. I don't think it needs its own RFC; It can probably go into the omnibus deprecations RFC for this version. (Which should get going very soon.)
--Larry Garfield
I think this makes sense, but it would definitely need to go through a Deprecation phase. I don't think it needs its own RFC; It can probably go into the omnibus deprecations RFC for this version. (Which should get going very soon.)
--Larry Garfield
Hi Larry, agreed on the deprecation phase, that's what I had in mind for 8.6.
On standalone vs omnibus I'm easy either way, still getting a feel for
how that's usually decided, so I'd rather not lock it in yet and see
how the thread develops.
Thanks,
Osama Aldemeery
I think this makes sense, but it would definitely need to go through a Deprecation phase. I don't think it needs its own RFC; It can probably go into the omnibus deprecations RFC for this version. (Which should get going very soon.)
--Larry Garfield
Hi Larry, agreed on the deprecation phase, that's what I had in mind for 8.6.
On standalone vs omnibus I'm easy either way, still getting a feel for
how that's usually decided, so I'd rather not lock it in yet and see
how the thread develops.Thanks,
Osama Aldemeery
I'm working on finalizing the 8.6 deprecation list https://wiki.php.net/rfc/deprecations_php_8_6
If you want to chug it in there, feel free to email me (or contact me via another medium) with the content of the proposal.
Ideally DokuWiki format, but I'll work with any format.
Best regards,
Gina P. Banyard
I'm working on finalizing the 8.6 deprecation list https://wiki.php.net/rfc/deprecations_php_8_6
If you want to chug it in there, feel free to email me (or contact me via another medium) with the content of the proposal.
Ideally DokuWiki format, but I'll work with any format.Best regards,
Gina P. Banyard
Hi Gina, I think that would be great, thank you.
I'll get the proposal content together in DokuWiki and send it over.
Best,
Osama
Hi all,
I'd like to gauge interest in deprecating
returninside afinallyblock, before I request karma to propose it as a full RFC.A
returninside afinallysilently discards whatever thetry/catchwas doing...a pending exception or return value just disappears, with no error or notice.As far as I know, it's the only abrupt exit from a
finallyPHP leaves silent...
break/continue/gotoout of one are already compile errors, and athrowauto-chains the discarded exception as$previous. Onlyreturndestroys the in-flight state and says nothing.Looking into the history of
finallyI found that the author of the original RFC said he added it only because Java allowed it, even though he thought it made "no sense" https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).I also found that it was the source of a few bugs:
https://bugs.php.net/bug.php?id=70228
https://bugs.php.net/bug.php?id=72213
https://github.com/php/php-src/issues/11028To see what a change would actually cost, I implemented the deprecation and ran it over the top ~5000 Composer packages. And I found that it's rare...there were only 12 occurrences across 9 packages out of 4992. Of those, 3 look like genuine latent bugs the deprecation caught, and the rest look deliberate.
Here's the list of the occurrences:
ibexa/admin-ui (swallows the exceptions its own docblock declares)
shopware/core (eats thumbnail failures and still reports success)
amphp/http-server (drops exceptions when the stream is already gone)
dvdoug/PHPCoord (deliberate...finally is the method's return)
dvdoug/PHPCoord (again)
dvdoug/PHPCoord (again)
dvdoug/PHPCoord (again)
spatie/ray (delibrate...any failure just returns false)
cakephp/cakephp (catch consumed the exception...finally just returns array)
hyperf/http-server (catch consumed the exception...finally emits response)
silverstripe/framework (catch consumed the mysqli error)
symfony/flex (try can't actually throw)My take here is that the language already handles the analogous cases (
continue/break/goto/...etc) natively, so leavingreturnlooks like an inconsistency.I lean toward deprecating it now with an eye to an error in a future major (following the steps of Tim's deprecate-return-from-constructor RFC)...though I guess a plain warning would be fine too. The main thing is that it seems worth doing something about.
Is there appetite for this?
Thanks,
Osama
Hi Osama,
I think for the sake of consistency ( and sanity ), this makes sense.
A deprecation phase in 8.x seems appropriate.
Cheers,
Seifeddine.
Hey Osama,
Hi all,
I'd like to gauge interest in deprecating
returninside afinally
block, before I request karma to propose it as a full RFC.A
returninside afinallysilently discards whatever the
try/catchwas doing...a pending exception or return value just
disappears, with no error or notice.As far as I know, it's the only abrupt exit from a
finallyPHP
leaves silent...
break/continue/gotoout of one are already compile errors, and a
throwauto-chains the discarded exception as$previous. Only
returndestroys the in-flight state and says nothing.Looking into the history of
finallyI found that the author of the
original RFC said he added it only because Java allowed it, even
though he thought it made "no sense"
https://externals.io/message/61670#61678
But Java itself warns about it (javac -Xlint:finally).I also found that it was the source of a few bugs:
- https://bugs.php.net/bug.php?id=70228
- https://bugs.php.net/bug.php?id=72213
- https://github.com/php/php-src/issues/11028
To see what a change would actually cost, I implemented the
deprecation
https://github.com/php/php-src/compare/master...aldemeery:php-src:deprecate-return-in-finally
and ran it over the top ~5000 Composer packages. And I found that it's
rare...there were only 12 occurrences across 9 packages out of 4992.
Of those, 3 look like genuine latent bugs the deprecation caught, and
the rest look deliberate.Here's the list of the occurrences:
- ibexa/admin-ui (swallows the exceptions its own docblock declares)
https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834- shopware/core (eats thumbnail failures and still reports success)
https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294- amphp/http-server (drops exceptions when the stream is already
gone)
https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322- dvdoug/PHPCoord (deliberate...finally is the method's return)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221- dvdoug/PHPCoord (again)
https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247- spatie/ray (delibrate...any failure just returns false)
https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71- cakephp/cakephp (catch consumed the exception...finally just
returns array)
https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755- hyperf/http-server (catch consumed the exception...finally emits
response)
https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140- silverstripe/framework (catch consumed the mysqli error)
https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213- symfony/flex (try can't actually throw)
https://github.com/symfony/flex/blob/4a6d98e/src/PackageResolver.php#L88My take here is that the language already handles the analogous cases
(continue/break/goto/...etc) natively, so leavingreturnlooks
like an inconsistency.I lean toward deprecating it now with an eye to an error in a future
major (following the steps of Tim's deprecate-return-from-constructor
RFC
https://wiki.php.net/rfc/deprecate-return-value-from-construct)...though
I guess a plain warning would be fine too. The main thing is that it
seems worth doing something about.Is there appetite for this?
Thanks,
Osama
I dislike this.
Yes, it's a rare occurence. But it's used legitimately in most places
you found. (as you say, only 3 places were genuine issues.)
So, why do you want to remove this? What's the point? It has some proper
usage sites.
We do have well-defined behaviour for return in finally. It works
properly (by now) - it's not like it's a frequent source of bugs - there
were a couple ones when initially introduced 10+ years ago, and a single
one more recent.
Also, no, it's not inconsistent - goto, continue etc. break out of the
finally block and continue the functions execution. A return stops
function execution as well. These are two different considerations.
Feels a lot like "let's try changing something for the sake of it". No
thanks.
Thanks,
Bob
I dislike this.
Yes, it's a rare occurence. But it's used legitimately in most places you
found. (as you say, only 3 places were genuine issues.)So, why do you want to remove this? What's the point? It has some proper
usage sites.We do have well-defined behaviour for return in finally. It works
properly (by now) - it's not like it's a frequent source of bugs - there
were a couple ones when initially introduced 10+ years ago, and a single
one more recent.Also, no, it's not inconsistent - goto, continue etc. break out of the
finally block and continue the functions execution. A return stops function
execution as well. These are two different considerations.Feels a lot like "let's try changing something for the sake of it". No
thanks.Thanks,
Bob
Hi Bob,
First, on framing, even though what I am suggesting here is removing it, I
deliberately left the door open to a plain warning if people here
preferred. The thing is that this is a silent footgun worth addressing...
So I'll admit I didn't expect "leave it entirely" to be on the table for
something that discards the in-flight state silently.
On the "why" itself, it's not that the behavior is broken, it works exactly
as defined. The problem is what it's defined to do, a return in finally
silently discards a pending exception or return value, with no diagnostic
at all. That silence is the whole case. And it's where the real damage is.
The genuine cases I found are exactly that...a controller that swallows the
very exceptions its own docblock declares it throws, a service that reports
success after an I/O failure, and an HTTP driver that drops exceptions when
a stream is gone. Nothing surfaces any of it, which is why they ship and
stay shipped.
So the benefit here is that we close a tiny gap that makes behavior less
surprising and more predictable, and the cost of addressing that is close
to nothing. It's in 0.18% of the top 5000 packages, and the deliberate uses
don't even need it, the same behavior can be achieved in other ways
(sometimes just one line away...the return just past the finally). So it
removes no capability, it just makes the silent cases visible.
On consistency, you're right...the jump bans are different. But that
doesn't remove the concrete issue = the silent discard. That stays
unchanged.
Thanks,
Osama