Re: Examples comparing Block Scoped RAII and Context Managers

php.internals

Frederik Bosch | Genkgo

289 days ago
On Sat, 15 Nov 2025 23:11:44 +0000, Rowan Tommins [IMSoP] wrote:
> Hi all, > > The Block Scoping RFC and the Context Manager RFC cover a lot of similar > use cases, and a lot of the discussion on both threads has been > explicitly comparing them. > > To try to picture better how they compare, I have put together a set of > examples that implement the same code using both features, as well as > some other variations, in this git repo: > > https://gitlab.com/imsop/raii-vs-cm > > A few notes: > > - The syntax for the two proposals is based on the current RFC text. If > they are updated, e.g. to use different keywords, I will update the > examples. > > - I have included examples with closures which automatically capture by > value, since a lot of the same use cases come up when discussing those. > > - There are many scenarios which could be included, and many ways each > example could be written. I have chosen scenarios to illustrate certain > strengths and weaknesses, but tried to fairly represent a "good" use of > each feature. However, I welcome feedback about unintentional bias in my > choices. > > - Corrections and additional examples are welcome as Merge Requests to > the repo, or replies here. > > > With that out of the way, here are my own initial thoughts from working > through the examples: > > - RAII + block scope is most convenient when protecting an existing > object which can be edited or extended. > > - When protecting a final object, or a native resource, RAII is harder > to implement. In these cases, the separation of Context Manager from > managed value is powerful. > > - Context Managers are very concise for safely setting and resetting > global state. RAII can achieve this, but feels less natural. > > - An "inversion of control" approach (passing in a callback with the > body of the protected block) requires capturing all variables *not* > scoped to the block. Even with automatic by-value capture, those needed > *after* the block would need to be listed for capture by reference. > > - Building a Context Manager from a Generator can lead to very readable > code in some cases, and closely mimics an "inversion of control" > approach without the same variable capture problems. > > > I would be interested in other people's thoughts. > > Regards, > > -- > Rowan Tommins > [IMSoP]
Another suggestion would be to follow the Java try-with-resources syntax. It does not require a new keyword to be introduced, as with the Context Manager syntax. Moreover, it aligns with current try-catch-finally usage already implemented by PHP developers. try ($transaction = $db->newTransaction()) {     $db->execute('UPDATE tbl SET cell = :cell', ['cell'=>'value']); } Any object that implements TryWithContext can be used with such syntax. The function returns the exit context operation as callback. interface TryWithContext {    public function tryWith(): \Closure; } For a transaction it might look like this. Rather auto-capture I'd suggest explicit complete scope capture, by using the use keyword without parenthesis. class Transaction implements TryWithContext {     public function tryWith(): \Closure     {         $this->db->beginTransaction();         return function (?\Throwable $e = null) use {             if ($e) {                 $this->db->rollbackTransaction();                 return;             }             $this->db->commitTransaction();         };     } }