Re: [RFC] Context Managers

From: Date: Mon, 30 Mar 2026 18:48:05 +0000
Subject: Re: [RFC] Context Managers
References: 1 2 3  Groups: php.internals 
Request: Send a blank email to internals+get-130500@lists.php.net to get a copy of this message
On Sun, Mar 29, 2026, at 6:14 AM, Tim Düsterhus wrote: > Larry, > > Am 2026-03-22 19:19, schrieb Larry Garfield: >> Arnaud and I have made a number of changes to the RFC that should make >> it sleaker and more consistent. The notable ones (that impact >> behavior) are as follows: > > “Sleaker” is not a word my dictionary understands. Was this a typo for > “sleeker” in the sense of “refined”? Yes, typo. Sleeker in the sense of "fewer bumpy parts on it." >> 1. We went back and forth on the continue question several >> times, >> before coming to the conclusion that continue is a tool for >> looping >> structures only. That switch also uses it is just >> switch being >> silly because reasons, and there is no reason using must >> inherit its >> weirdness. Therefore, continue inside a >> using block now means >> nothing at all. continue will ignore it, the same way it >> ignores an >> if statement. > > I fail to see how “making break; and continue; behave inconsistently” is > making the RFC (and by extension) the language any more consistent. In > this following example snippet it's not at all obvious that the > break > is behaving incorrectly by targeting the using() with > continue > targeting the foreach(), despite both using the same > “number”: > > $processed = 0; > foreach ($entries as $entry) { > using ($db->transaction()) { > switch ($entry['type']) { > case 'EOF': > break 2; > default: > if (should_skip($entry)) { > continue 2; > } > > $db->insert($entry); > } > } > > $processed++; > } > > I'm also noticing that the RFC still does not explain *why* the decision > for break to target using() has > been made. For your reference, my > last email asking that question is this one: > https://news-web.php.net/php.internals/129771. I didn't > receive a reply > to that email either (and neither do the list archives have a reply). I'm pretty sure I did explain in this thread somewhere... In short, we want a way to be able to terminate the using block early in a success case. An error case is easy (throw), but for a success case we cannot use return, as that will return from the function. Technically "goto and your own label" would work, but I really hope we don't need to get into a discussion about why making goto the only way to solve something is a bad idea... break is the natural keyword for that, as it already means "stop this control structure and go to the end of it." continue means "stop this iteration of a control structure and go to the next one." But in this case, there is no next one. switch makes it an alias for break, for whatever reason lost to history, but given that it now throws a warning that seems to now be considered a mistake, so we don't see a reason to propagate that mistake. >> 2. Several people (including us) were uncomfortable with using a >> boolean return from the exitContext() method. While that is what >> Python does, it is indeed not self-evident how it works. (Should true >> mean "true, I'm done" or "true, rethrow"?) We debated using an >> enum >> value, but that appeared to be too verbose. >> >> Instead, we decided that exitContext() should return ?Throwable, which >> is the same thing it is passed. In a success case, it is passed null.. >> In a failure case, it is passed a throwable. So it can then return >> null (meaning "we're done, nothing else to do here") or a throwable, >> which will then get thrown. Since in most cases an error should be >> allowed to propagate, it means simply calling return >> $exception at >> the end of the method will "do the right thing" 95% of the time. >> Simple and easy and self-documenting. (If there's a reason to wrap and >> rethrow the exception, do that and return the new exception. Or to >> swallow the exception and not propagate it, return null.) > > That sounds like a “throw” statement with extra steps [1]. While nothing > stopped you from writing throw new SomeException(); within > exitContext() with the bool > return value, it at least *encouraged* > you to not replace the original Exception with another Exception > entirely and to just make a decision between “suppress” or “not > suppress”. Now return is completely equivalent to > throw (at least as > long as exitContext() doesn't contain a > catch() itself) adding even > more layers of “using() is magically including behavior of other > language constructs” that will be hard to reason about for humans and > machines alike. If you have an alternate suggestion for how to achieve this functionality, now is the time to propose it. Behaviors in the order they're likely to happen (I'd expect): - Success case, there is no exception - Keep propagating the exception. - The exception stops here. - We're catching the exception and wrapping it in another exception with more useful data on it (exceptions can do that), and then throwing that. The setup we have now solves all four cases with fairly self-evident code, and handles the first two cases in the exact same code so most people won't need to really think about it. If you have a better suggestion, please do share. --Larry Garfield

« previous php.internals (#130500) next »