On 30/06/2022 11:29, Arnaud Le Blanc wrote:

I feel that the RAII pattern aka SBRM / Scope-Bound Resource Management is not
relevant in PHP context, and I don't believe that it's commonly used in PHP or
in garbage collected language.


I've used a simple version of the pattern effectively to implement transactions: if the Transaction object goes out of scope without being explicitly committed or rolled back, it assumes the program hit an unexpected error condition and rolls back.


One way the lifetime of a value could be extended is via a reference cycle.
These are easy to introduce and difficult to prevent or observe (e.g. in a
test or in an assertion).


I would expect reference cycles to be pretty rare in most code, particularly when you're dealing with a value with a short lifetime as is involved in most RAII scenarios.

The worst-case release of the cycle can also be made predictable by running gc_collect_cycles()


An other way would be by referencing the value
somewhere else. You can not guarantee that the lifetime of a value is
unaffected after passing it to a function.


Surely the only way to avoid that is with something like Rust's "borrow checker"? Otherwise, any function that has a reference to something can extend the lifetime of that reference by storing it inside some other structure with a longer lifetime. Manually freeing the underlying resource then just leads to a "use after free" error.


Another factor that makes RAII un-viable in PHP is that the order of the
destructor calls is unspecified. Currently, if multiple objects go out of
scope at the same time, they happen to be called in a FIFO order, which is not
what is needed when using the RAII pattern [0][1].


I can imagine this would be a problem for some advanced uses of the pattern, but for a simple "acquire lock, release on scope exit" or "start transaction, rollback on unexpected scope exit", it's generally not relevant.


Other languages typically have other ways to explicitly manage the lifetime of
resources. Go has `defer()` [2]. Python has context managers / `with` [3], C#
has `using` [4]. `with` and `using` can be implemented in userland in PHP.


My understanding is that C#'s "using" is indeed about deterministic destruction, but Pythons's "with" is a more powerful inversion-of-control mechanism. I would actually really love to have some version of Python's context managers in PHP, and think it would be a better alternative to closures in a lot of cases.

For instance, a motivation cited in support of auto-capture is something like this:

function doSomething($a, $b, $c) {
   return $db->doInTransaction(fn() {
       // use $a, $b, and $c
       // roll back on exception, commit otherwise
       return $theActualResult;
   }
}

But this is actually quite a "heavy" implementation: we create a Closure, capture values, enter a new stack frame, and have two return statements, just to wrap the code in try...catch...finally boilerplate.

The equivalent with a context manager would look something like this:

function doSomething($a, $b, $c) {
   with ( $db->startTransaction() as $transaction ) {
       // use $a, $b, and $c
       // roll back on exception, commit otherwise
       return $theActualResult;
   }
}

Here, the with statement doesn't create a new stack frame, it just triggers a series of callbacks for the boilerplate at the start and end of the block. No variables need to be captured, because they are all still available, and "return" returns from the doSomething() function, not the transaction wrapper.

The explanation of how Python's implementation works and why is an interesting read: https://peps.python.org/pep-0343/


Regards,

--
Rowan Tommins
[IMSoP]

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to