Query Bus
The Query Bus plugin dispatches queries (read operations) to their corresponding handlers and returns data without modifying state.
Two adapters are available:
- SyncQueryBus executes queries synchronously with automatic handler resolution.
- CachedQueryBus decorates any QueryBus to cache responses for cacheable queries.
Why Use It
In CQRS architecture, read operations are represented as queries. The Query Bus provides a single dispatch point that resolves the appropriate handler based on naming convention (ListBooksQuery → ListBooksHandler). Unlike commands, queries return a response object and have no side effects.
Usage
The bus dispatches a query and returns a response:
$query = new ListBooksQuery();
$response = $this->queryBus->dispatch($query);
// $response is ListBooksResponse
Queries can include filter parameters:
final readonly class SearchBooksQuery implements QueryInterface
{
public function __construct(
public string $searchTerm,
public int $page = 1
) {}
}
Handlers return a QueryResponseInterface:
public function handle(QueryInterface $query): QueryResponseInterface
{
$books = $this->bookRepository->findAll();
return new ListBooksResponse($books);
}
CachedQueryBus
The CachedQueryBus decorator adds caching to any QueryBus implementation using the Cache plugin.
Opt-in Caching
Only queries implementing CacheableQueryInterface are cached:
final readonly class ListBooksQuery implements CacheableQueryInterface
{
public function __construct(
public int $page = 1
) {}
public function getCacheTtl(): DateInterval|int|null
{
return 300; // 5 minutes, or null for default TTL
}
}
Non-cacheable queries pass through directly to the inner bus.
Standalone Usage
$cachedBus = new CachedQueryBus(
innerQueryBus: $syncQueryBus,
cache: $cache,
logger: $logger,
defaultTtl: 300, // Default TTL when query returns null
);
$response = $cachedBus->dispatch(new ListBooksQuery());
Cache Key Generation
Cache keys are generated automatically from the query class name and its properties:
Properties implementing IdInterface or Stringable are normalized to strings for consistent key generation.
RedisCache Configuration
When using CachedQueryBus with RedisCache, the QueryResponse classes must be whitelisted for secure deserialization. Configure the allowed classes in your container:
CacheInterface::class => fn (ContainerInterface $c): RedisCache => new RedisCache(
$c->get(Redis::class),
[
ListBooksResponse::class,
DetailBookResponse::class,
// Add all response classes from CacheableQueryInterface queries
],
),
See Cache - RedisCache Security for details on this security feature.
Testing
The SyncQueryBus can be instantiated directly in tests. For testing components that use QueryBus, use the fake:
$queryBus = new Tests\Phexium\Fake\Plugin\QueryBus\QueryBus();
$queryBus->stubResponse(ListBooksQuery::class, new ListBooksResponse([]));
$response = $queryBus->dispatch(new ListBooksQuery());
expect($queryBus->hasDispatched(ListBooksQuery::class))->toBeTrue();
See Also
- CQRS - Query Bus enables CQRS pattern
- Bus Mode - Primary architectural pattern
- Queries & Handlers - What the bus dispatches
- Cache - Cache plugin used by CachedQueryBus