On 05/11/2025 22:38, Bob Weinand wrote:
The choice of adding the exception to the exitContext() is
interesting, but also very opinionated:
- It means, that the only way to abort, in non-exceptional cases, is
to throw yourself an exception. And put a try/catch around the with()
{} block. Or manually use enterContext() & exitContext() - with a fake
"new Exception" essentially.
- Maybe you want to hold a transaction, but just ensure that
everything gets executed together (i.e. atomicity), but not care about
whether everything actually went through (i.e. not force a rollback on
exception). You'll now have to catch the exception, store it to a
variable, use break and check for the exception after the with block.
Or, yes, manually using enterContext() and exitContext().
The Context Manager is *given knowledge of* the exception, but it's not
obliged to change its behaviour based on that knowledge. I don't think
that makes the interface opinionated, it makes it extremely flexible.
It means you *can* write this, which is impossible in a destructor:
function exitContext(?Throwable $exception) {
if ( $exception === null ) {
$this->commit();
} else {
$this->rollback();
}
}
But you could also write any of these, which are exactly the same as
they would be in __destruct():
// Rollback unless explicitly committed
function exitContext(?Throwable $exception) {
if ( ! $this->isCommitted ) {
$this->rollback();
}
}
// Expect explicit commit or rollback, but roll back as a safety net
function exitContext(?Throwable $exception) {
if ( ! $this->isCommitted && ! $this->isRolledBack ) {
$this->logger->warn('Transaction went out of scope without
explicit rollback, rolling back now.');
$this->rollback();
}
}
// User can choose at any time which action will be taken on destruct / exit
function exitContext(?Throwable $exception) {
if ( $this->shouldCommitOnExit ) {
$this->commit();
} else {
$this->rollback();
}
}
You could also combine different approaches, using the exception as an
extra signal only if the user hasn't chosen explicitly:
function exitContext(?Throwable $exception) {
if ($this->isCommitted || $this->isRolledBack) {
return;
}
if ( $exception === null ) {
$this->logger->debug('Implicit commit - consider calling
commit() for clearer code.');
$this->commit();
} else {
$this->logger->debug('Implicit rollback - consider calling
rollback() for clearer code.');
$this->rollback();
}
}
--
Rowan Tommins
[IMSoP]