Skip to content

Queries & Handlers

Queries request data without modifying state. They are part of the CQRS pattern in Phexium's Bus Mode.

Queries

Queries define what data is requested and any filtering criteria:

final readonly class ListBooksQuery implements QueryInterface {}

final readonly class DetailBookQuery implements QueryInterface
{
    public function __construct(public string $bookId) {}
}

Characteristics:

  • Immutable
  • Named: verb + noun + Query (e.g., ListBooksQuery)
  • May contain filters
  • No side effects

Handlers

Handlers retrieve data and return QueryResponseInterface objects:

public function __invoke(ListBooksQuery $query): ListBooksResponse
{
    $books = $this->bookRepository->findAll();

    $bookItems = [];
    foreach ($books as $book) {
        $bookItems[] = [
            'id' => $book->getId()->getValue(),
            'title' => $book->getTitle()->getValue(),
            'status' => $book->getStatus()->value,
        ];
    }

    return new ListBooksResponse($bookItems);
}

Handler responsibilities:

  1. Fetch data from repository
  2. Transform to primitive values
  3. Return response

Dispatching

$query = new ListBooksQuery();
$response = $this->queryBus->dispatch($query);
$viewModel = $this->presenter->present($response)->getViewModel();

Handlers are resolved by naming convention: ListBooksQueryListBooksHandler.

Form Rendering Queries

Some queries carry form state (pre-filled values and validation errors) for rendering create/update forms. This pattern keeps form rendering in the query pipeline, benefiting from presenters and ViewModels:

final readonly class CreateBookQuery implements QueryInterface
{
    public function __construct(
        public array $formValues = [],
        public array $errors = []
    ) {}
}

The controller dispatches this query both for initial form display (empty values) and after validation failure (with values and errors). The handler echoes the form state into the response for the presenter to format.

This is an intentional trade-off: the query does not fetch domain data, but it flows through the same bus/presenter/ViewModel pipeline, ensuring consistent rendering. The demo application uses this pattern for CreateBookQuery, UpdateBookQuery, and BorrowBookQuery.

Data Transformation

Transform domain entities to primitives in handlers:

// Do: Return primitives
$bookItems[] = ['id' => $book->getId()->getValue()];

// Don't: Return domain entities
$bookItems[] = $book;  // Leaks domain to presentation

See Also