Installation
composer require averor/query-bus
Register the service provider in config/app.php:
'providers' => [
// ...
Averor\QueryBus\QueryBusServiceProvider::class,
],
Basic Usage
Define a query class (e.g., GetUserQuery):
namespace App\Queries;
use Averor\QueryBus\Query;
class GetUserQuery implements Query
{
public function __construct(public int $userId) {}
}
Create a handler (e.g., GetUserQueryHandler):
namespace App\Handlers;
use App\Queries\GetUserQuery;
use Averor\QueryBus\QueryHandler;
class GetUserQueryHandler implements QueryHandler
{
public function handle(GetUserQuery $query): string
{
return "User ID: {$query->userId}";
}
}
Dispatch the query in a controller:
use Averor\QueryBus\QueryBus;
class UserController
{
public function __construct(private QueryBus $queryBus) {}
public function show(int $id)
{
$result = $this->queryBus->ask(new GetUserQuery($id));
return response()->json($result);
}
}
Automatic Handler Registration
The package auto-discovers handlers in app/Handlers by default. No manual binding needed.
GetUserByEmailQuery, ListActiveOrdersQuery).class GetUserWithOrdersQuery implements Query
{
public function __construct(
public int $userId,
public ?int $limit = null
) {}
}
public function handle(GetUserWithOrdersQuery $query): array
{
$user = $this->userRepository->find($query->userId);
$orders = $this->queryBus->ask(new ListOrdersQuery($query->userId, $query->limit));
return compact('user', 'orders');
}
class GetUserQueryHandler implements QueryHandler
{
public function __construct(private UserRepository $repository) {}
public function handle(GetUserQuery $query): User
{
return $this->repository->find($query->userId);
}
}
class UserService
{
public function __construct(private QueryBus $queryBus) {}
public function getUserProfile(int $id): array
{
$user = $this->queryBus->ask(new GetUserQuery($id));
$stats = $this->queryBus->ask(new GetUserStatsQuery($id));
return array_merge($user, $stats);
}
}
namespace App\Middleware;
use Averor\QueryBus\QueryMiddleware;
use Psr\Log\LoggerInterface;
class QueryLogger implements QueryMiddleware
{
public function __construct(private LoggerInterface $logger) {}
public function handle($query, callable $next)
{
$this->logger->info("Query dispatched: " . get_class($query));
return $next($query);
}
}
Register in config/query-bus.php:
'middleware' => [
\App\Middleware\QueryLogger::class,
],
$this->queryBus->askAsync(new GetUserQuery($id))
->then(fn($result) => $this->dispatch(new UserProcessed($result)));
app/Handlers or explicitly configured in config/query-bus.php:
'handler_namespace' => 'App\\Handlers\\Queries',
QueryHandler and match the query class name (e.g., GetUserQueryHandler for GetUserQuery).public function handle(GetUserQuery $query): User
{
return Cache::remember("user.{$query->userId}", now()->addHours(1), fn() =>
$this->repository->find($query->userId)
);
}
QueryHandler interface.try-catch to avoid breaking the bus:
public function handle($query, callable $next)
{
try {
return $next($query);
} catch (\Exception $e) {
report($e);
throw $e;
}
}
Averor\QueryBus\QueryBus to add features (e.g., retry logic):
class CustomQueryBus extends QueryBus
{
public function askWithRetry(Query $query, int $retries = 3)
{
try {
return $this->ask($query);
} catch (\Exception $e) {
if ($retries <= 0) throw $e;
return $this->askWithRetry($query, $retries - 1);
}
}
}
bind() to register handlers manually:
$this->app->bind(GetUserQuery::class, fn() => new GetUserQueryHandler());
QueryBus to return stubbed results:
$queryBus = Mockery::mock(QueryBus::class);
$queryBus->shouldReceive('ask')
->withArgs([new GetUserQuery(1)])
->andReturn(new User());
$this->app->instance(QueryBus::class, $queryBus);
$handler = new GetUserQueryHandler($mockRepository);
$this->assertEquals($expectedUser, $handler->handle(new GetUserQuery(1)));
How can I help you explore Laravel packages today?