On 07/05/2026 13:16, Bob Weinand wrote:
I am not opposed at all to such a consistent macro system though.
... but I would not make a transaction like that a macro. For inlining 
variables in a html or sql snippet with proper escaping, definitely great.
To me, macros should meaningfully transform their contained code rather than 
wrapping a bit of logic around, which would be covered by this RFC.
It's complimentary, not a replacement.


Thinking about this further, I agree that full-blooded macros would be overkill for this case, but my starting point was that closures don't feel like the right solution either - they're a different feature being bent into shape.

So rather than starting with a Closure, and taking things away, I was trying to think of ways to start with a simple block of code, and build it up.


For instance, the array_walk example in the RFC is just an ugly (and probably inefficient) way of writing a foreach loop:

array_walk($numbers, fn($number) {
    $count = bcadd($count, '1');
    $sum = bcadd($sum, $number);
});

foreach($numbers as $number) {
    $count = bcadd($count, '1');
    $sum = bcadd($sum, $number);
}

So, what would it look like to generalise that, so we could do the same for other scenarios, like async() and transaction()?


A simple first step would be something that could take a block of code, and say when to execute it:

custom_block transaction($dbConnection) {
    try {
        $dbConnection->beginTransaction();
        __execute_block_body();
    }
    catch (\Throwable $e) {
        $dbConnection->rollbackTransaction();
        throw $e;
     }
     $dbConnection->commitTransaction();
}

transaction($myDb) {
    $myDb->query('UPDATE ...');
}

If the block body is never represented as a variable, there's no need to define what happens when it outlives scope, or the user tries to clone it, rebind it, etc.

And if the calling code doesn't look like it's creating a Closure, users won't have any wrong expectations about variable scopes or lifetimes.


Onto that, we can add syntax to push values into the block, e.g. using the "as" keyword like in "foreach" (example based on the "Generator decorator managers" section at the end of the Context Managers RFC):

custom_block opening($filename) {
    $f = fopen($filename, "r");
    if (!$f) {
        throw new Exception("fopen($filename) failed");
    }
    try {
        __execute_block_body($f);
    } finally {
        fclose($f);
    }
}

opening(__FILE__ as $f) {
    var_dump($f);
}


And some way to pull values out, e.g. a "break with" keyword:

custom_block transaction(DBConnection $conn) {
  $conn->beginTran();
  try {
    if (__execute_block_body() === DBConnection::TRANSACTION_ABORT) {
      $conn->rollbackTran();
      return;
    }
  } catch (\Throwable $e) {
    $conn->rollback();
    throw $e;
  }
  $conn->commitTran();
}

transaction($connection) {
    $affectedRows = $connection->query("UPDATE ...");
    if ($affectedRows === 0) {
        break with DBConnection::TRANSACTION_ABORT;
    }
    // ...
}


I'm sure there are details here that wouldn't quite work as expressed, but hopefully it explains what I meant by wanting something closer to macros than closures.


Regards,

--
Rowan Tommins
[IMSoP]

Reply via email to