Skip to content

Transactions? Not your problem!

In most PHP projects, transaction management falls on the developer. But a forgotten DB::transaction() or a missing rollback, and the data sometimes ends up in an inconsistent state.

In Phexium, the TransactionalCommandBus is a decorator of the CommandBus. It implements the same interface, and on every dispatch, it wraps execution in a transaction:

$this->transaction->begin();
try {
    $this->innerCommandBus->dispatch($command);
    $this->transaction->commit();
} catch (Throwable $e) {
    $this->transaction->rollback();
    throw $e;
}

On the handler side, there is no trace of transaction management. The handler does its save(), dispatches its events. The begin/commit/rollback cycle lives in the decorator, not in the handler.

Why this approach?

Other strategies exist for managing transactions in a CQRS architecture:

  • Transaction in the Handler: the most common default approach, but it is an anti-pattern in Clean Architecture. The handler mixes business logic and technical concerns, violating the single responsibility principle.
  • Unit of Work: relevant in an ORM ecosystem (Doctrine, Eloquent), but overkill for a lightweight framework that does without. The transactional decorator is simpler and more explicit.
  • Middleware Pipeline: the most flexible and composable approach. But as long as the transaction remains the only cross-cutting concern on the bus, the decorator offers the right level of simplicity.
  • AOP: elegant in theory with a declarative #[Transactional] annotation, but not idiomatic in PHP and hard to debug in practice.

The transactional decorator sits at the sweet spot: it respects SOLID, remains simple and explicit, and evolves naturally into a pipeline as needs grow.