Newsgroups: php.internals
Path: news.php.net
Xref: news.php.net php.internals:129315
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 B9D231A00BC
for ; Wed, 19 Nov 2025 22:19:28 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail;
t=1763590773; bh=mib4MXjpVaCm3IZBgMobRf75E0zvF706KYixpFCLu+M=;
h=Date:Subject:To:References:From:In-Reply-To:From;
b=Oku3jmL4eG7L0j6mINVTBb6wR4JaXrfselSySyYj4hbFO3hxl+VPIvqIvbubLl6tz
uMC130VXJPBqkNa4FPfb13VbfzFO0VulE9umFEQIXVQkxmG8ALytocF/kx33VTknq5
lZUDl9kbdbv79ghbh8AHVYYoHEYJ9kEMRJ5yOBqqc2ut1NXVCTXtw6cbmXbe08y4I/
NbQl+4vDOGwCUUe95KswEfO547MHyGBsCK0sW19QwAxmXLvbWrB58BhfkmtZcHt+I6
R3BmOSIF0D64j/v6Zh0mm9FqJG8uuARk1of7oERbKHT5Bwpoa7WD5e1Od1avqOte5W
y08Uw/j2GJuXw==
Received: from php-smtp4.php.net (localhost [127.0.0.1])
by php-smtp4.php.net (Postfix) with ESMTP id E94BD1801ED
for ; Wed, 19 Nov 2025 22:19:31 +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.9 required=5.0 tests=BAYES_20,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, 19 Nov 2025 22:19:31 +0000 (UTC)
Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45])
by mailfhigh.stl.internal (Postfix) with ESMTP id 9986F7A01CC
for ; Wed, 19 Nov 2025 17:19:25 -0500 (EST)
Received: from phl-mailfrontend-02 ([10.202.2.163])
by phl-compute-05.internal (MEProxy); Wed, 19 Nov 2025 17:19:25 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; 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=1763590765; x=1763677165; bh=f7uQ7XO4SN
QJqB0v047dWmZ/rZ+W8rgsngOYW1iwFoY=; b=QTfvbl3FYVRJFSwQAudBB1uVSH
EBNqdGHAmv7GKaSYp5ylpXttkqhI9Xp42MVT4/b6nMumiwgW5UNQCgCRCSAFiXpm
ckXamIAhrcRWQESO7NQXbo5Ppe0RcDKteAUOdV5o2b9d60CjYT5I49yrRPaK7nqX
oYR7piiY0xg3EEKk0dn6NiyqRfoa5uli/wpwQjEPwMPDs1rCYoj7qFol/Bj3zJiN
SwNzr3UuJy1xdQC0LEvjjhetrGAyZX2BMmcjgKOYLRdFbaWJH5/O3+ydQrOLutFC
N/5lmhN1+W47WSNuQ5N0nKGBfofLcVlOsNjDIk0B2ityFZqnGDLybDdyAazw==
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=fm3; t=
1763590765; x=1763677165; bh=f7uQ7XO4SNQJqB0v047dWmZ/rZ+W8rgsngO
YW1iwFoY=; b=fYH9eYyauf6cto+/i7zcRXqWKxL2aGF+qV3c5rapsq+Qgp4dTB6
5mNbLHo8uF4dMY1jfwZsAWt0yD2ZK/GZrqFVCvutgOE5zV6O1qLdBi2XjTzjbQfQ
VKUkTc4m1a/2CnXUvDsrvFsNA04yWpNqiEHiWTqH5dsGuCx4/c4HMlQJbp6R/BDd
1/k6G/DnINHV9smGpQIem8E9VvWLvNyMAAXjplhqNk/QZlmV43VKJ3siUg3sYdDt
i7iMHEbCnolamNfwmCee7HYZ+B+1dehdcACjAyxWRH9Q7dxEwQPBX9K7LTEnM334
LAHKpKYZ9Cuqw69OXXvl5zJ1lp+zoTsKeUw==
X-ME-Sender:
X-ME-Received:
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddvvdehfeekucetufdoteggodetrf
dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu
rghilhhouhhtmecufedttdenucenucfjughrpegtkfffgggfuffvfhfhjgesrgdtreertd
dvjeenucfhrhhomhepfdftohifrghnucfvohhmmhhinhhsucglkffoufhorfgnfdcuoehi
mhhsohhprdhphhhpsehrfigvtgdrtghordhukheqnecuggftrfgrthhtvghrnhepheetle
eiiefgueduieeuieffvdevheduueefkeejuefgffeftdeitdegtedtleetnecuvehluhhs
thgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepihhmshhophdrphhhph
esrhifvggtrdgtohdruhhkpdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhu
thdprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght
X-ME-Proxy:
Feedback-ID: id5114917:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA for
; Wed, 19 Nov 2025 17:19:24 -0500 (EST)
Content-Type: multipart/alternative;
boundary="------------PpqcY7v5LK0WbzOA8P4HWra3"
Message-ID:
Date: Wed, 19 Nov 2025 22:19:23 +0000
Precedence: list
list-help:
list-unsubscribe:
list-post:
List-Id:
x-ms-reactions: disallow
MIME-Version: 1.0
User-Agent: Mozilla Thunderbird
Subject: Re: [PHP-DEV] Examples comparing Block Scoped RAII and Context
Managers
To: internals@lists.php.net
References: <26a2f13c-f318-4d6c-9595-bfaaebcbabcb@rwec.co.uk>
<432ca4ad-7bcc-43bd-8e05-3121839b4ff7@app.fastmail.com>
Content-Language: en-GB
In-Reply-To: <432ca4ad-7bcc-43bd-8e05-3121839b4ff7@app.fastmail.com>
From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]")
This is a multi-part message in MIME format.
--------------PpqcY7v5LK0WbzOA8P4HWra3
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
On 18/11/2025 17:23, Larry Garfield wrote:
> One thing I definitely do not like is the need for a `FileWrapper` class in the RAII file-handle example. That seems like an unnecessary level of abstraction just to squeeze the `fclose()` value onto the file handle. The fully-separated Context Manager seems a more flexible approach.
Yes, exploring how exactly that flexibility could be used was part of my
motivation for the examples I picked.
The downside is that it is slightly harder to understand at first
glance: someone reading "using (file_for_write('file.txt') as $fh)"
might well assume that $fh is the value returned from
"file_for_write('file.txt')", rather than the value returned from
"file_for_write('file.txt')->enterContext()".
What made sense to me was comparing to an Iterator that only goes around
once - in "foreach (files_to_write_to() as $fh)", the
"files_to_write_to()" call doesn't return $fh either,
"files_to_write_to()->current()" does.
> I also noted that all of the examples wrap the context block (of whichever syntax) in a try-catch of its own. I don't know if that's going to be a common pattern or not. If so, might it suggest that the `using` block have its own built-in optional `catch` and `finally` for one-off additional handling? That could point toward the Java approach of merging this functionality into `try`, but I am concerned about the implications of making both `catch` and `finally` effectively optional on `try` blocks. I am open to discussion on this front. (Anyone know what the typical use cases are in Python?)
Looking at the parser, I realised that a "try" block with neither
"catch" nor "finally" actually matches the grammar; it is only rejected
by a specific check when compiling the AST to opcodes. Without that
check, it would just compile to some unnecessary jump table entries.
I guess an alternative would be allowing any statement after the using()
rather than always a block, as in Seifeddine and Tim's proposal, which
allows you to stack like this:
using ($db->transactionScope()) try {
// ...
}
catch ( SomeSpecificException $e ) {
// ...
}
Or, the specific combination "try using( ... )" could be added to the
parser. (At the moment, "try" must always be followed by "{".)
As I noted in one of the examples
(file-handle/application/1b-raii-with-scope-block.php), there is a
subtle difference in semantics between different nesting orders - with
"try using()", you can catch exceptions thrown by enterContext() and
exitContext(); with "using() try", you can catch exceptions before
exitContext() sees them and cleans up.
It seems Java's try-with-resources is equivalent to "try using()":
> In a try-with-resources statement, any catch or finally block is run
after the resources declared have been closed.
> Regarding `let`, I think there's promise in such a keyword to opt-in to "unset this at the end of this lexical block." However, it's also off topic from everything else here, as I think it's very obvious now that the need to do more than just `unset()` is common. Sneaking hidden "but if it also implements this magic interface then it gets a bonus almost-destructor" into it is non-obvious magic that I'd oppose. I'd be open to a `let` RFC on its own later (which would likely also make sense in `foreach` and various other places), but it's not a solution to the "packaged setup/teardown" problem.
I completely agree. I think an opt-in for block scope would be useful in
a number of places, and resource management is probably the wrong focus
for designing it. For instance, it would give a clear opt-out
for capture-by-default closures:
function foo() {
// ... code setting lots of variables ...
$callback = function() use (*) {
let $definitelyNotCaptured=null;
// ... code mixing captured and local variables ...
}
}
> Which is exactly the benefit of the separation of the Context Manager from the Context Variable. The CM can be written to rely on `unset()` closing the object (risk 2), or to handle closing it itself (risk 1), as the developer determines.
Something the examples I picked don't really showcase is that a Context
Manager doesn't need to be specialised to a particular task at all, it
can generically implement one of these strategies.
The general pattern is this:
class GeneralPurposeCM implements ContextManager {
public function __construct(private object $contextVar) {}
public function enterContext(): object { return $this->contextVar; }
public functoin exitContext(): void {}
}
- On its own, that makes "using(new GeneralPurposeCM(new Something) as
$foo) { ... }" a very over-engineered version of "{ let $foo = new
Something; ... }"
- To emulate C#, constrain to "IDisposable $contextVar", and call
"$this->contextVar->Dispose()" in exitContext()
- To emulate Java, constrain to "AutoCloseable $contextVar" and call
"$this->contextVar->close()" in exitContext()
- To throw a runtime error if the context variable still has references
after the block, swap "$this->contextVar" for a WeakReference in
beginContext(); then check for "$this->contextVarWeakRef->get() !==
null" in exitContext()
- To have objects that "lock and unlock themselves", constrain to
"Lockable $contextVar", then call "$this->contextVar->lock()" in
beginContext() and "$this->contextVar->unlock()" in exitContext()
The only things you can't emulate are:
1) The extra syntax options provided by other languages, like C#'s
"using Something foo = whatever();" or Go's "defer
some_function(something);"
2) Compile-time guarantees that the Context Variable will not still have
references after the block, like in Hack. I don't think that's a
realistic goal for PHP.
Incidentally, while checking I had the right method name in the above, I
noticed the Context Manager RFC has an example using "leaveContext"
instead, presumably an editing error. :)
Regards,
--
Rowan Tommins
[IMSoP]
--------------PpqcY7v5LK0WbzOA8P4HWra3
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
On 18/11/2025 17:23, Larry Garfield
wrote:
One thing I definitely do not like is the need for a `FileWrapper` class in the RAII file-handle example. That seems like an unnecessary level of abstraction just to squeeze the `fclose()` value onto the file handle. The fully-separated Context Manager seems a more flexible approach.
Yes, exploring how exactly that flexibility could be used was
part of my motivation for the examples I picked.
The downside is that it is slightly harder to understand at first
glance: someone reading "using (file_for_write('file.txt') as
$fh)" might well assume that $fh is the value returned from
"file_for_write('file.txt')", rather than the value returned from
"file_for_write('file.txt')->enterContext()".
What made sense to me was comparing to an Iterator that only goes
around once - in "foreach (files_to_write_to() as $fh)", the "files_to_write_to()"
call doesn't return $fh either, "files_to_write_to()->current()"
does.
I also noted that all of the examples wrap the context block (of whichever syntax) in a try-catch of its own. I don't know if that's going to be a common pattern or not. If so, might it suggest that the `using` block have its own built-in optional `catch` and `finally` for one-off additional handling? That could point toward the Java approach of merging this functionality into `try`, but I am concerned about the implications of making both `catch` and `finally` effectively optional on `try` blocks. I am open to discussion on this front. (Anyone know what the typical use cases are in Python?)
Looking at the parser, I realised that a "try" block with neither
"catch" nor "finally" actually matches the grammar; it is only
rejected by a specific check when compiling the AST to opcodes.
Without that check, it would just compile to some unnecessary jump
table entries.
I guess an alternative would be allowing any statement after the
using() rather than always a block, as in Seifeddine and Tim's
proposal, which allows you to stack like this:
using ($db->transactionScope()) try {
// ...
}
catch ( SomeSpecificException $e ) {
// ...
}
Or, the specific combination "try using( ... )" could be added to
the parser. (At the moment, "try" must always be followed by "{".)
As I noted in one of the examples
(file-handle/application/1b-raii-with-scope-block.php), there is a
subtle difference in semantics between different nesting orders -
with "try using()", you can catch exceptions thrown by
enterContext() and exitContext(); with "using() try", you can
catch exceptions before exitContext() sees them and cleans up.
It seems Java's try-with-resources is equivalent to "try
using()":
> In a try-with-resources statement, any catch or finally
block is run after the resources declared have been closed.
Regarding `let`, I think there's promise in such a keyword to opt-in to "unset this at the end of this lexical block." However, it's also off topic from everything else here, as I think it's very obvious now that the need to do more than just `unset()` is common. Sneaking hidden "but if it also implements this magic interface then it gets a bonus almost-destructor" into it is non-obvious magic that I'd oppose. I'd be open to a `let` RFC on its own later (which would likely also make sense in `foreach` and various other places), but it's not a solution to the "packaged setup/teardown" problem.
I completely agree. I think an opt-in for block scope would be
useful in a number of places, and resource management is probably
the wrong focus for designing it. For instance, it would give a
clear opt-out for capture-by-default closures:
function foo() {
// ... code setting lots of variables ...
$callback = function() use (*) {
let $definitelyNotCaptured=null;
// ... code mixing captured and local variables ...
}
}
Which is exactly the benefit of the separation of the Context Manager from the Context Variable. The CM can be written to rely on `unset()` closing the object (risk 2), or to handle closing it itself (risk 1), as the developer determines.
Something the examples I picked don't really showcase is that a
Context Manager doesn't need to be specialised to a particular
task at all, it can generically implement one of these strategies.
The general pattern is this:
class GeneralPurposeCM implements ContextManager {
public function __construct(private object $contextVar) {}
public function enterContext(): object { return
$this->contextVar; }
public functoin exitContext(): void {}
}
- On its own, that makes "using(new GeneralPurposeCM(new
Something) as $foo) { ... }" a very over-engineered version of "{
let $foo = new Something; ... }"
- To emulate C#, constrain to "IDisposable $contextVar", and call
"$this->contextVar->Dispose()" in exitContext()
- To emulate Java, constrain to "AutoCloseable $contextVar" and
call "$this->contextVar->close()" in exitContext()
- To throw a runtime error if the context variable still has
references after the block, swap "$this->contextVar" for a
WeakReference in beginContext(); then check for
"$this->contextVarWeakRef->get() !== null" in exitContext()
- To have objects that "lock and unlock themselves", constrain to
"Lockable $contextVar", then call
"$this->contextVar->lock()" in beginContext() and
"$this->contextVar->unlock()" in exitContext()
The only things you can't emulate are:
1) The extra syntax options provided by other languages, like
C#'s "using Something foo = whatever();" or Go's "defer
some_function(something);"
2) Compile-time guarantees that the Context Variable will not
still have references after the block, like in Hack. I don't think
that's a realistic goal for PHP.
Incidentally, while checking I had the right method name in the
above, I noticed the Context Manager RFC has an example using
"leaveContext" instead, presumably an editing error. :)
Regards,
--
Rowan Tommins
[IMSoP]
--------------PpqcY7v5LK0WbzOA8P4HWra3--