Middleware on the bus is where the transaction belongs

Wrapping a handler in a transaction inside the handler means every handler repeats it, and the one that forgets is the one that leaves half a write.

framework:
  messenger:
    buses:
      command.bus:
        middleware:
          - validation
          - doctrine_transaction
          - AppMessengerCorrelationIdMiddleware

# the handler is now free of both concerns and testable without either

Ordering matters and is not obvious: validation before the transaction means an invalid message never opens one, and the correlation middleware belongs outside both so that a rollback is still logged with its id. A custom middleware is one method receiving the envelope and a callable for the rest of the stack, which is the same shape as HTTP middleware and immediately legible. Putting a transaction on an event bus rather than a command bus is usually a mistake, since events are supposed to be independent.