Re: [RFC] Context Managers
| From: | Rowan Tommins [IMSoP] | Date: | Mon, 19 Jan 2026 18:10:43 +0000 |
| Subject: | Re: [RFC] Context Managers | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129791@lists.php.net to get a copy of this message | ||
On 19/01/2026 15:58, Larry Garfield wrote:
In an ideal world, if we had auto-capturing long-closures, then I would agree this is largely unnecessary and could instead be implemented like so (to reuse the examples from the RFC): $conn->inTransaction(function () { // SQL stuff. }); ... If we had auto-capturing closures, I would probably argue that is a better approach.I haven't caught up with the discussion fully, but I want to pick up on this specifically, because I disagree. Inversion of control like this would only be suitable in the general case if we had auto-capture *by reference*. I believe every proposal so far has limited automatic capture to *values only*. Auto-capture by value helps you get values *into* the closure, but does not help get anything back *out*. So, if the code you want to sugar looks like this: ``` try{ $db->beginTransaction(); // ... $newFooId = $db->execute('INSERT INTO Foo ... RETURNING FooId'); $newBarId = $db->execute('INSERT INTO Foo ... RETURNING BarId'); // ... $db->commitTransaction(); } finally{ if( $db->isInTransaction()) { $db->rollbackTransaction(); } } // use $newFooId and $newBarId here
Then your options with a capture-by-value closure are either
a) list the outputs as manual by-ref captures:
``` $newFooId = $newBarId = null;
$db->inTransaction(fn() use (&$newFooId, &$newBarId) {
// ...
$newFooId = $db->execute('INSERT INTO Foo ... RETURNING FooId');
$newBarId = $db->execute('INSERT INTO Foo ... RETURNING BarId');
// ...
}); // use $newFooId and $newBarId here
or b) return the outputs, and extract them using array destructuring or similar:
``` [$newFooId, $newBarId] = $db->inTransaction(fn() {
// ...
$newFooId = $db->execute('INSERT INTO Foo ... RETURNING FooId');
$newBarId = $db->execute('INSERT INTO Foo ... RETURNING BarId');
// ...
return [$newFooId, $newBarId];});// use $newFooId and $newBarId here
A Context Manager - or any other syntax based on a code block rather than a full stack frame - instead gives you direct access to the local variables:
``` using($db->transaction()) {
// ...
$newFooId = $db->execute('INSERT INTO Foo ... RETURNING FooId');
$newBarId = $db->execute('INSERT INTO Foo ... RETURNING BarId');
// ...
}// use $newFooId and $newBarId here
Even if we had automatic capture by value, I think Context Managers would be a useful proposal to discuss.
--
Rowan Tommins
[IMSoP]