[PRE-RFC] Deprecate returning from a finally block

php.internals

Osama Aldemeery

74 days ago
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: 1. ibexa/admin-ui (swallows the exceptions its own docblock declares) <https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834> 2. shopware/core (eats thumbnail failures and still reports success) <https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294> 3. amphp/http-server (drops exceptions when the stream is already gone) <https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322> 4. dvdoug/PHPCoord (deliberate...finally is the method's return) <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131> 5. dvdoug/PHPCoord (again) <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148> 6. dvdoug/PHPCoord (again) <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221> 7. dvdoug/PHPCoord (again) <https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247> 8. spatie/ray (delibrate...any failure just returns false) <https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71> 9. cakephp/cakephp (catch consumed the exception...finally just returns array) <https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755> 10. hyperf/http-server (catch consumed the exception...finally emits response) <https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140> 11. silverstripe/framework (catch consumed the mysqli error) <https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213> 12. 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

Gina P. Banyard

74 days ago
On Thursday, 18 June 2026 at 23:01, Osama Aldemeery <aldemeery@gmail.com> wrote: > 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 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

Osama Aldemeery

74 days ago
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 Fri, Jun 19, 2026 at 12:47 PM Gina P. Banyard <internals@gpb.moe> wrote:

Larry Garfield

74 days ago
On Thu, Jun 18, 2026, at 4:49 PM, Osama Aldemeery wrote:
> 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: > 1. ibexa/admin-ui (swallows the exceptions its own docblock declares) > <https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834> > 2. shopware/core (eats thumbnail failures and still reports success) > <https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294> > 3. amphp/http-server (drops exceptions when the stream is already > gone) > <https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322> > 4. dvdoug/PHPCoord (deliberate...finally is the method's return) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131> > 5. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148> > 6. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221> > 7. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247> > 8. spatie/ray (delibrate...any failure just returns false) > <https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71> > 9. cakephp/cakephp (catch consumed the exception...finally just > returns array) > <https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755> > 10. hyperf/http-server (catch consumed the exception...finally emits > response) > <https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140> > 11. silverstripe/framework (catch consumed the mysqli error) > <https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213> > 12. 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
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

Osama Aldemeery

73 days ago
> 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

Gina P. Banyard

73 days ago
On Saturday, 20 June 2026 at 00:45, Osama Aldemeery <aldemeery@gmail.com> wrote:
> > 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

Osama Aldemeery

73 days ago
> 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

Seifeddine Gmati

73 days ago
On Thu, 18 Jun 2026 at 22:58, Osama Aldemeery <aldemeery@gmail.com> wrote:
> > 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 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 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)...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.

Bob Weinand

73 days ago
Hey Osama, On 18.6.2026 23:49:11, Osama Aldemeery wrote:
> 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: > > 1. ibexa/admin-ui (swallows the exceptions its own docblock declares) > <https://github.com/ibexa/admin-ui/blob/2322d54/src/bundle/Controller/ContentTypeController.php#L834> > 2. shopware/core (eats thumbnail failures and still reports success) > <https://github.com/shopware/core/blob/e8079a0/Content/Media/Thumbnail/ThumbnailService.php#L294> > 3. amphp/http-server (drops exceptions when the stream is already > gone) > <https://github.com/amphp/http-server/blob/b306134/src/Driver/Http2Driver.php#L1322> > 4. dvdoug/PHPCoord (deliberate...finally is the method's return) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/CompoundPoint.php#L131> > 5. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeocentricPoint.php#L148> > 6. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4/src/Point/GeographicPoint.php#L221> > 7. dvdoug/PHPCoord (again) > <https://github.com/dvdoug/PHPCoord/blob/ced02c4ad44aa4a558f69d53af4834a9d50ab2aa/src/Point/ProjectedPoint.php#L242-L247> > 8. spatie/ray (delibrate...any failure just returns false) > <https://github.com/spatie/ray/blob/2da2079/src/Client.php#L71> > 9. cakephp/cakephp (catch consumed the exception...finally just > returns array) > <https://github.com/cakephp/cakephp/blob/eef91f28de119bee5536905244d2096f752f2920/src/Database/Query.php#L1748-L1755> > 10. hyperf/http-server (catch consumed the exception...finally emits > response) > <https://github.com/hyperf/http-server/blob/80c52d4/src/Server.php#L140> > 11. silverstripe/framework (catch consumed the mysqli error) > <https://github.com/silverstripe/silverstripe-framework/blob/9c59c42/src/ORM/Connect/MySQLiConnector.php#L213> > 12. 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
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

Osama Aldemeery

73 days ago
On Sat, Jun 20, 2026 at 4:25 PM Bob Weinand <bobwei9@hotmail.com> wrote:
> 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