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]