Handling DB transaction in closure
There's a couple of things I'd recommend you change about your current code, and there's an alternative to your using a closure's use construct to pass in other variables.

The first thing I'd do, is to change your function's signatures to type-hint the expected types for each argument, ie change the transaction function to look something more like this:

function transaction(PDO $conn, callable $closure)
{}


Optionally, replacing the callable type-hint with Closure. The upside of callable is that old-school callable constructs will be accepted, too (array($object, 'someMethod') or even ucfirst), the downside is, of course that functions like ucfirst are accepted, but clearly not a valid function in this context.

Anyway, apart from using use to pass in additional variables, you could add a third argument to the transaction function, and type-hint an array:

function transaction(PDO $conn, callable $closure, array $arguments = [])
{}


Then, simply invoke the $closure argument using call_user_func_array to pass the arguments that need to be passed:

array_unshift($conn, $arguments);//prepend $conn to the arguments 
call_user_func_array($closure, $arguments);


And you're there... Of course, if you find that array_unshift call clumsy looking (which it kind of is), you can just assume the $arguments array to contain the PDO connection from the off, in which case:

function transaction(callable $callback, array $arguments)
{
    if (!$arguments[0] instanceof PDO) {
        throw new InvalidArgumentException('arguments should contain PDO instance');
    }
    $conn = $arguments[0];
    try {
        $conn-&gt;beginTransaction();
        call_user_func_array($callback, $arguments);
        $conn-&gt;commit();
    } catch (Exception $e) {
        $conn-&gt;rollBack();
        throw $e;
    }
}


Again, there is a downside to this approach, the main disadvantages, IMO, are:


Overhead (a PHP closure is syntactic sugar, it's actually creating a new instance of the Closure class.
The $arguments array will often be a bit too complex for its own good, leaving you with code that actually calls your transaction function to decide on the actual logic -> every call can look different, and often will contain SQL strings, fetch calls and the like. Ugly code is harder to maintain...
Like you said: the stack trace looses some of its relevance, making debugging harder. 


The last two issues (code that is harder to maintain + harder to debug) are, to me, severe problems, so much so, that I'd recommend not to use the transaction function, but instead write the try-catch blocks where and when I need them.