On 27/11/2025 08:34, Tim Düsterhus wrote:
Hi Tim,
As promised, I eventually got back to this e-mail properly, and have
updated the examples in response.
For convenience, here's the link again: https://gitlab.com/imsop/raii-vs-cm
The block scoping RFC has been updated to `let()`.
Updated.
1.
For db-transaction/implementation/1-raii-object.php I'd like to note
that it is not necessary to proxy execute() through the transaction
object. It could also be used as a simple guard object which only
purpose is to be constructed and destructed.
That's true. The "wikimedia-at-ease" example illustrates that style,
because there aren't any methods we'd want there at all.
The drawback I see is that on a longer block, you have to come up with a
name for that unused variable, and make sure you don't accidentally
unset or overwrite it.
Apparently Java's workaround for that is to allow "unnamed variables",
which have the expected lifetime, but can't be accessed:
https://openjdk.org/jeps/456
try-with-resources is given as one of the example use cases ("var"
infers the type; "_" in place of the name makes it an "unnamed variable"):
try (var _ = ScopedContext.acquire()) {
... no use of acquired resource ...
}
Notably, this is *not* the same meaning for "_" as, say, C#, where "_ =
foo()" tells the compiler to discard the return value of foo():
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards
In the case of a transaction, it feels logical for at least the commit()
and rollback() methods to be on the Transaction class, but other methods
would be a matter of style. I've removed execute() to take the simplest
route.
2.
The RAII object in 'file-handle' serves no purpose. PHP will already
call `fclose()` when the resource itself goes out of scope, so this is
existing behavior with extra steps. The same is true for the
0-linear-code example in file-object. You don't need the `fclose()`
there.
This is true as long as nothing stores an additional reference to the
file handle. Having a separate "guard object" doesn't fully prevent this
- a reference to the guard object itself could leak - but does make it
less likely.
It also gives somewhere to customise the open and close behaviour, such
as adding locking, or converting false-on-error to an exception (more on
that below).
To see what it looks like, I've added a "file-handle-unguarded"
scenario, which exposes the handle much more directly: it doesn't force
fclose(), and leaves the application to handle the failure of fopen()
The CM example benefits from being able to "break" out of the using{}
block; as far as I can see, the current RFC doesn't allow that from a
let{} block, is that correct?
3.
The docblock in locked-pdf/application/2-context-manager.php is
incorrectly copy and pasted.
Well spotted. Fixed.
I was about to comment that the 'file' examples were not equivalent,
because the context manager and IOC ones didn't include the error
logging, until I noticed it was hidden away in the implementation.
This probably suggests that I implicitly expected to see all relevant
control flow. Exception handling and logging in particular probably
greatly depend on the surrounding context to be useful (e.g. to adapt
the log message or to enhance it with additional context data). So
while it might superficially look cleaner / simpler, I feel that this
kind of generic handling will bite you sooner or later. So with the
context manager example, I would expect the “exception introspection”
capability to be used to properly tear down the context, but not for
cross-cutting concerns such as logging.
That's a reasonable comment. Looking at those examples more closely, I
realise I actually messed up the error handling in most of them - they
never check for the false returned by fopen()
I've moved the logging out into the "application", but updated all the
"implementations" to throw a "FileOpeningException" if fopen() returns
false.
This really demonstrates the value of the "try using() { ... }"
short-hand Larry & Arnaud have added to the CM RFC - we need the try to
be on the *outside* of the block to catch the new exception. I presume a
similar "try let() { ... }" could be added to yours?
It's interesting how similar the example end up for simple scenarios,
but I think if we had block-scoped variables, I'd still like Context
Managers for some of the more complex cases.
--
Rowan Tommins
[IMSoP]