Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:130642 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 1D4851A00BC for ; Wed, 15 Apr 2026 15:52:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1776268327; bh=2eQLCUNf+u9nJzggcGiqDyheY6ptyngtTTkO1/P1+gI=; h=Date:From:To:In-Reply-To:References:Subject:From; b=dDfge7eLSxYGJc8foU0UOm6BpWO3XxHRGdhyMsLaKtWV7jXMba8PBHuGH9anA+Fhb u6sLnR90VRx/MmYT7YM0NJrEOF6HYB2BUDGm+VsHTyOuG3j7u1Z0TXvNtSuYgzD8t3 jMTqG75rWFzZx7UkukWCWT1+OCklELiGye+L8naYulrsfoyHyL9f3Ahli9Estiq9TB cxdjk/gUbVh/TsYi1PSrwyclvlRMS8Z5fVoo7nqI6g7vVLPdKyX/LTC9y3jev1kWR5 c24VIBOzcxqOJTikM18sakTatpYHyF8u7w5c8qIXY3UZYcnS9DJ8P8rggUOvjqqY8j DTMMknUCePfRA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 59FE11801EF for ; Wed, 15 Apr 2026 15:52:06 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-b5-smtp.messagingengine.com (fhigh-b5-smtp.messagingengine.com [202.12.124.156]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 15 Apr 2026 15:51:56 +0000 (UTC) Received: from phl-compute-12.internal (phl-compute-12.internal [10.202.2.52]) by mailfhigh.stl.internal (Postfix) with ESMTP id A93EC7A00EA for ; Wed, 15 Apr 2026 11:51:50 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-12.internal (MEProxy); Wed, 15 Apr 2026 11:51:50 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1776268310; x=1776354710; bh=1HnOsV5RWl DYtyFtukV+R6Oyn4WO1c07ts5wknyCwsk=; b=lyQbIwH8sYPfaaIcH1/HLwDPjq Zevh29Wt8Coc0spralR+P6uaQTbsi2lGADgfVQIGmbRk7y7nWBgyzcD0UYvPNjCO lHjHD7vBnpoxuZr5EVDQvcYioG8Sc7Y5vXHiAqozLTNCplU40Fbetl6c8rtW2ZhA nTQJnD8r4gMfNI4aXEfvPMFQw+eXuq6pY4mt8pL4PWpaccX5Tau3p9Qa/RZWRhPN i6VheH8ipDwtSOQtShOqGPVCbdQBujBVDckc1FiF7b+KRm3TyaMzM//g+26RnGmR +rSWLWS6WYSyCLswCSkE8lj9oGfU7mcgthjJ0kT6Wb9LT5SXF9nC9ooIRKJQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1776268310; x=1776354710; bh=1HnOsV5RWlDYtyFtukV+R6Oyn4WO1c07ts5 wknyCwsk=; b=DsnsIJQ0yscEL/Rw2cR2nEnLzqYI2bo6v4wcXdgYW4W+S/3la+J DwvcXjAxKIpYWAHMOe1AXUSJ/s/vu9bqiQXEJ/jqQTHPFwAVeARQUW1rwRbrPklr sLxXEz+GRjI01opDOkH8yl7rYhC81tG/S2pZB13bFYFJVDtmGzTkqDRqVdRsJv57 GFUGUsJ3P/mRBAxZUzkXRf0+1EA+BUgt85IGKcu14PrPwTSr8XmAo9Korc4syqOm /2OkSB8sS0VgM3kIUUTenyk9L+AYkFuve7fhAIsAYE1jXhT0hWGz6g4CpduqEpPN hFt9+svqpQEUBymsXLAA29nJcFp9+bqyYYg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefhedrtddtgdeggeehtdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecunecujfgurhepofggfffhvffkjghfufgtsegrtderreertd ejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessghothhtlhgvugdr tghouggvsheqnecuggftrfgrthhtvghrnhepleekhedtgfefhfelieelgfegiefhkedvle efjedtffelhfehheffgfduteduuddtnecuffhomhgrihhnpehphhhprdhnvghtnecuvehl uhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessghoth htlhgvugdrtghouggvshdpnhgspghrtghpthhtohepuddpmhhouggvpehsmhhtphhouhht pdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 24D36182007A; Wed, 15 Apr 2026 11:51:50 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: AJqZv0gpVmjF Date: Wed, 15 Apr 2026 17:51:28 +0200 To: internals@lists.php.net Message-ID: In-Reply-To: References: <4985896b-c80a-4302-912e-9f572a260fb5@app.fastmail.com> Subject: Re: [PHP-DEV] [RFC] Context Managers Content-Type: multipart/alternative; boundary=429a01d07bf3a7355df1abad3b418d74cb89a909 From: rob@bottled.codes ("Rob Landers") --429a01d07bf3a7355df1abad3b418d74cb89a909 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Wed, Apr 15, 2026, at 16:52, Larry Garfield wrote: > On Tue, Apr 14, 2026, at 9:27 AM, Rob Landers wrote: > > On Tue, Apr 14, 2026, at 16:18, Rob Landers wrote: > >>=20 > >>=20 > >> On Tue, Nov 4, 2025, at 21:13, Larry Garfield wrote: > >>> Arnaud and I would like to present another RFC for consideration: = Context Managers. > >>>=20 > >>> https://wiki.php.net/rfc/context-managers > >>>=20 > >>> You'll probably note that is very similar to the recent proposal f= rom Tim and Seifeddine. Both proposals grew out of casual discussion se= veral months ago; I don't believe either team was aware that the other w= as also actively working on such a proposal, so we now have two. C'est = la vie. :-) > >>>=20 > >>> Naturally, Arnaud and I feel that our approach is the better one. = In particular, as Arnaud noted in an earlier reply, __destruct() is unr= eliable if timing matters. It also does not allow differentiating betwe= en a success or failure exit condition, which for many use cases is abso= lutely mandatory (as shown in the examples in the context manager RFC). > >>>=20 > >>> The Context Manager proposal is a near direct port of Python's app= roach, which is generally very well thought-out. However, there are a f= ew open questions as listed in the RFC that we are seeking feedback on. > >>>=20 > >>> Discuss. :-) > >>>=20 > >>> --=20 > >>> Larry Garfield > >>> larry@garfieldtech.com > >>>=20 > >>=20 > >> Hi Larry/Arnaud, > >>=20 > >> This is a pretty exciting thread and fascinating proposal. That bei= ng said, I have a couple of subtle questions that don't seem to be answe= red in the (very long) thread or the RFC itself -- If I missed it, pleas= e let me know: >=20 > >> 1. What happens if a Fiber is suspended in the using block and ne= ver resumed? When is the using block released to clean up the context? >=20 > Since it decomposes to a try-catch-finally, it will exit whenever the = finally block would have run if you'd just typed out try-catch-finally y= ourself. Arnaud checked, and confirmed that when the fiber is destroyed= the using block will exit in a success case (ie, exitContext(null)). >=20 > >> 2. There's still no mention of how this should affect debugging, w= ill we see the "desugared" or "sugared" version? Is that even a concern = for the RFC? >=20 > Error messages would see the original code, so "error on line X" would= be based on the original `using` block. That's the same as any other d= esugaring we already do. (PIpes, PFA, constructor promotion, etc.) Deb= uggers will see the materialized opcodes, again, the same other desugari= ng cases. >=20 > >> 3. I will say it is weird to have exitContext return an exception;= but what happens if an exception is thrown during exitContext? Why not = just have it return void and throw if you need to throw instead of havin= g two paths to the same thing? >=20 > There's a subtle but important difference here: An exception passed th= rough exitContext() is the original exception from lower in the call sta= ck, and its backtrace will be the original location of the error. An ex= ception thrown from within exitContext() itself indicates a failure that= the Context Manager is responsible for, usually an error in the exitCon= text() logic itself. >=20 > Technically a Context Manager can wrap-and-rethrow the exception if it= wants, but then it is "claiming ownership" over it, just like in any ot= her case of wrap-and-rethrow. >=20 > Our expectation is that 90% of the time, "let the exception propagate = up unimpeded" is the desired behavior. This approach makes "return $e" = the right thing to do almost-always, which is nice and simple to remembe= r. >=20 > See the "return values and exception handling" section for a discussio= n of this in more detail. As I said in a previous reply, our constraint= s are different than Python's so we end up with a different solution. I= f you have a suggestion for an alternate approach to the problem, we're = happy to listen. >=20 > >> 4. Looking at the desugared form ... I'm a bit confused: if exitCo= ntext is called during the finally path and returns an exception, it is = just swallowed? But if it is thrown, it won't be? >=20 > The finally path is only reached in case of a successful exit. Theref= ore there is no exception to pass in, and thus returning an exception is= meaningless. If exitContext() throws a new exception of its own (which= would indicate an error in its own logic), that will just bubble up pa= st the `using` block entirely, which is what we want. >=20 > >> 5. That being said, I don't think the RFC shares with us when we s= hould return an exception vs. throw an exception. >=20 > See the "return values and exception handling" section. If something = there isn't clear, let me know and I will try to clarify further. >=20 > >> =E2=80=94 Rob > > > > Maybe the desugared version should look more like this? > > > > } catch (\Throwable $e) { > > try { > > $__mgr->exitContext($e); > > } catch (\Throwable $cleanupException) { > > throw new ContextManagerException( > > $cleanupException->getMessage(), > > previous: $e > > ); > > } > > throw $e; > > } > > >=20 > I'm not sure I see a reason to force any new exceptions to be only of = the ContextManagerException type. If there's a TypeError inside exitCon= text() or something, I'd expect that to be propagated as a TypeError. >=20 > --Larry Garfield Thanks Larry, this clears things up for me. The example I had in mind is= distinguishing between errors in a transaction: class DatabaseTransaction implements ContextManager { public function __construct(private PDO $connection) {} public function enterContext(): PDO { $this->connection->beginTransaction(); return $this->connection; } public function exitContext(?\Throwable $e =3D null): ?\Throwable { if ($e) { $this->connection->rollback(); // PDO throws: server has gon= e away } else { $this->connection->commit(); } return $e; } } // Application code: using ($db->transaction() =3D> $conn) { $conn->execute('INSERT INTO orders ...'); // throws ValidationExcept= ion } The application will see the PDOException about the server going away, b= ut the original ValidationException that caused the rollback attempt is = lost entirely. My point with my suggestion wasn't to hide an exception b= ehind a specific type, but to use exception chaining native to PHP to pr= eserve both independent failures in a way that an application can unders= tand what actually happened. In other words, as a developer, all I'd see is a rollback failed due to = a server disconnection. I'd be missing what caused the rollback in the f= irst place. I'd be fine if the desugared catch path simply attached the original exc= eption as `$previous` on whatever escapes `exitContext()`, so the root c= ause isn't lost. That's a one-line change in the engine: just set the pr= evious property at the bottom of the cleanup exception's chain before re= throwing. =E2=80=94 Rob --429a01d07bf3a7355df1abad3b418d74cb89a909 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Wed, Apr = 15, 2026, at 16:52, Larry Garfield wrote:
On Tue, Apr 14, 2026, at 9:27 AM, Rob Landers= wrote:
> On Tue, Apr 14, 2026, at 16:18, Rob Landers wrote= :
>> 
>> 
>> O= n Tue, Nov 4, 2025, at 21:13, Larry Garfield wrote:
>>&g= t; Arnaud and I would like to present another RFC for consideration: Con= text Managers.
>>> 
>>> 
>&= gt;> You'll probably note that is very similar to the recent proposal= from Tim and Seifeddine.  Both proposals grew out of casual discus= sion several months ago; I don't believe either team was aware that the = other was also actively working on such a proposal, so we now have two.&= nbsp; C'est la vie. :-)
>>> 
>>&= gt; Naturally, Arnaud and I feel that our approach is the better one.&nb= sp; In particular, as Arnaud noted in an earlier reply, __destruct() is = unreliable if timing matters.  It also does not allow differentiati= ng between a success or failure exit condition, which for many use cases= is absolutely mandatory (as shown in the examples in the context manage= r RFC).
>>> 
>>> The Context = Manager proposal is a near direct port of Python's approach, which is ge= nerally very well thought-out.  However, there are a few open quest= ions as listed in the RFC that we are seeking feedback on.
>= ;>> 
>>> Discuss. :-)
>>&g= t; 
>>> -- 
>>> &nb= sp; Larry Garfield
>&g= t;> 
>> 
>> Hi Larry/Arnaud= ,
>> 
>> This is a pretty exciting = thread and fascinating proposal. That being said, I have a couple of sub= tle questions that don't seem to be answered in the (very long) thread o= r the RFC itself -- If I missed it, please let me know:

>>  1.  What happens if a Fiber is suspended in= the using block and never resumed? When is the using block released to = clean up the context?

Since it decomposes to a = try-catch-finally, it will exit whenever the finally block would have ru= n if you'd just typed out try-catch-finally yourself.  Arnaud check= ed, and confirmed that when the fiber is destroyed the using block will = exit in a success case (ie, exitContext(null)).

>>  2. There's still no mention of how this should affect de= bugging, will we see the "desugared" or "sugared" version? Is that even = a concern for the RFC?

Error messages would see= the original code, so "error on line X" would be based on the original = `using` block.  That's the same as any other desugaring we already = do.  (PIpes, PFA, constructor promotion, etc.)  Debuggers will= see the materialized opcodes, again, the same other desugaring cases.

>>  3. I will say it is weird to have= exitContext return an exception; but what happens if an exception is th= rown during exitContext? Why not just have it return void and throw if y= ou need to throw instead of having two paths to the same thing?

There's a subtle but important difference here: An exce= ption passed through exitContext() is the original exception from lower = in the call stack, and its backtrace will be the original location of th= e error.  An exception thrown from within exitContext() itself indi= cates a failure that the Context Manager is responsible for, usually an = error in the exitContext() logic itself.

Techni= cally a Context Manager can wrap-and-rethrow the exception if it wants, = but then it is "claiming ownership" over it, just like in any other case= of wrap-and-rethrow.

Our expectation is that 9= 0% of the time, "let the exception propagate up unimpeded" is the desire= d behavior.  This approach makes "return $e" the right thing to do = almost-always, which is nice and simple to remember.

See the "return values and exception handling" section for a discu= ssion of this in more detail.  As I said in a previous reply, our c= onstraints are different than Python's so we end up with a different sol= ution.  If you have a suggestion for an alternate approach to the p= roblem, we're happy to listen.

>>  4= . Looking at the desugared form ... I'm a bit confused: if exitContext i= s called during the finally path and returns an exception, it is just sw= allowed? But if it is thrown, it won't be?

The = finally path is only reached in case of a successful exit.  Therefo= re there is no exception to pass in, and thus returning an exception is = meaningless.  If exitContext() throws a new exception of its own (w= hich would indicate an error in its own logic), that  will just bub= ble up past the `using` block entirely, which is what we want.

>>  5. That being said, I don't think the RFC= shares with us when we should return an exception vs. throw an exceptio= n.

See the "return values and exception handlin= g" section.  If something there isn't clear, let me know and I will= try to clarify further.

>> =E2=80=94 Rob=
>
> Maybe the desugared version should look m= ore like this?
>
> } catch (\Throwable $e) {
>     try {
>  &n= bsp;      $__mgr->exitContext($e);
>     } catch (\Throwable $cleanupException) {
>         throw new = ContextManagerException(
>     &nb= sp;       $cleanupException->getMessage= (),
>         =     previous: $e
>    &n= bsp;    );
>     }
=
>     throw $e;
> }
&= gt;

I'm not sure I see a reason to force any ne= w exceptions to be only of the ContextManagerException type.  If th= ere's a TypeError inside exitContext() or something, I'd expect that to = be propagated as a TypeError.

--Larry Garfield<= /div>

Thanks Larry, this clears things u= p for me. The example I had in mind is distinguishing between errors in = a transaction:

class DatabaseTransaction implements ContextManager=0A{=0A    pu=
blic function __construct(private PDO $connection) {}=0A=0A    public fu=
nction enterContext(): PDO=0A    {=0A        $this->connection->be=
ginTransaction();=0A        return $this->connection;=0A    }=0A=0A  =
  public function exitContext(?\Throwable $e =3D null): ?\Throwable=0A  =
  {=0A        if ($e) {=0A            $this->connection->rollback(=
); // PDO throws: server has gone away=0A        } else {=0A            =
$this->connection->commit();=0A        }=0A        return $e;=0A  =
  }=0A}=0A=0A// Application code:=0Ausing ($db->transaction() =3D>=
 $conn) {=0A    $conn->execute('INSERT INTO orders ...'); // throws V=
alidationException=0A}

The application will see= the PDOException about the server going away, but the original Validati= onException that caused the rollback attempt is lost entirely. My point = with my suggestion wasn't to hide an exception behind a specific type, b= ut to use exception chaining native to PHP to preserve both independent = failures in a way that an application can understand what actually happe= ned.

In other words, as a developer, all I'd se= e is a rollback failed due to a server disconnection. I'd be missing wha= t caused the rollback in the first place.

I'd b= e fine if the desugared catch path simply attached the original exceptio= n as $previous on = whatever escapes exitCont= ext(), so the root cause isn't lost. That's a one-line change in = the engine: just set the previous property at the bottom of the cleanup = exception's chain before rethrowing.

=E2=80=94 Rob
--429a01d07bf3a7355df1abad3b418d74cb89a909--