Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Query Bus Laravel Package

gnugat/query-bus

A lightweight query bus for PHP that dispatches queries to dedicated handlers, helping decouple read operations from application code. Includes middleware support for composing cross-cutting concerns around query execution.

View on GitHub
Deep Wiki
Context7

Getting Started

This release is the first stable cut of a small CQRS-style query bus: a QueryBus receives a query object, asks each registered QueryMatcher whether it can handle that query, and executes the matching one. There is no prior assessment to diff against, so treat v1.0.0 as the baseline.

Minimal steps:

  1. Install the package with Composer and confirm the service provider is auto-discovered (or register it manually in bootstrap/providers.php / config/app.php depending on your Laravel version).
  2. Read two classes first: QueryBus and the QueryMatcher contract. Everything else in the package is plumbing around these. The bus is the only thing you inject into your application code; matchers are the only thing you write.
  3. Check whether the package ships a config file (php artisan vendor:publish). If it does, publish it early so matcher registration is explicit rather than relying on defaults.
  4. Build one vertical slice end to end before writing abstractions: a FindUserById query, a FindUserByIdMatcher that returns a user DTO, registration of the matcher, and a controller that injects QueryBus and calls it.

The first use case should be a read that already exists in your codebase as a repository call. Wrapping an existing query in the bus is the cheapest way to validate that the dispatch/matching ergonomics fit how your team writes code, without committing to a full CQRS refactor.

Implementation Patterns

One matcher per query type. The common shape is a matcher whose matches() (or equivalent predicate) checks $query instanceof FindUserById and whose handler method performs the work. Keep matchers thin — they should translate a query into a call on an existing repository/service, not contain the query logic themselves.

Registration belongs in a service provider. Register matchers in a dedicated QueryServiceProvider rather than in AppServiceProvider. This keeps the bus wiring greppable and makes it obvious which queries the application supports. If the package offers a config-driven registration array, prefer the provider for type safety and IDE navigation, and use config only for environment-specific overrides.

Use priority deliberately for overlapping matchers. v1.0.0 adds prioritization at registration time. The useful pattern is a specific matcher plus a general fallback:

  • High priority: FindUserByIdMatcher (exact query type).
  • Low priority: a generic CachedQueryMatcher or LoggingQueryMatcher that handles a base query interface.

Without explicit priorities, correctness depends on registration order, which is fragile once providers are merged or config is cached. Whenever two matchers can match the same query, assign explicit priorities and add a test asserting which one wins.

Treat the bus as a boundary, not a service locator. Inject QueryBus into controllers, jobs, and console commands, but do not inject it into domain services or matchers. Matchers calling the bus recursively is the fastest way to get an untraceable dispatch chain and, with priority-based matching, a possible infinite loop.

Testing pattern. Because matching is centralized, you can test the routing independently of the handlers: register stub matchers with known priorities, dispatch a query, and assert the expected stub was invoked. Then test each real matcher in isolation as a plain unit. This split keeps bus tests cheap and handler tests focused. Where the package allows it, swap the container binding for a recording fake bus in feature tests so you assert that a query was dispatched rather than re-testing its result.

Integration tip: dispatch from form requests or actions, not models. Keeping dispatch calls in the HTTP/console layer preserves the option to remove the bus later without touching domain code.

Gotchas and Tips

Priority direction is the first thing to verify. The release notes state that matchers are prioritized but not which end of the range wins. Read the QueryBus sorting code (or write a two-matcher test) before relying on any numeric value. Getting this backwards produces silent misrouting rather than an error, which is expensive to debug in production.

Zero-match behaviour is undefined by the notes. Confirm what happens when no matcher matches — a specific exception type, a null return, or a generic RuntimeException. Wrap dispatch in your own exception if the package's is too generic, and add a test for the unmatched case; it is the failure mode you will hit most often after a refactor renames a query class.

Multiple matches may not be an error. If two matchers both claim a query and priorities tie, behaviour likely falls back to registration order. Do not assume the bus detects ambiguity. Defensive habit: make each matcher's predicate as narrow as possible (instanceof a concrete class, not a shared interface) and reserve broad interface matching for a single, clearly-labelled fallback.

Config caching can freeze registration. If matcher registration is config-driven, php artisan config:cache in deployment will bake in the priority list. Any dynamically registered matchers (for example, from a package's own provider) must be registered in a provider, not at runtime, or they will vanish after caching. Test with config:cache enabled at least once before shipping.

Debugging which matcher fired. Add a decorator around QueryBus — or a low-priority catch-all matcher used only in local environments — that logs the query class and the resolved matcher. This is far cheaper than adding temporary dump() calls inside matchers during an incident.

Extension points to watch. The QueryMatcher contract is the intended seam: decorators for caching, logging, timing, and authorization all fit there rather than in the bus. If you need cross-cutting behaviour on every query, wrap the bus itself in a decorator implementing the same interface and bind it in the container — this survives package upgrades better than subclassing QueryBus, whose internals (matching and priority sorting) are exactly what changed in this release.

Semantic versioning caveat. v1.0.0 is the first stable release, so the public surface is only now being pinned. Pin the version in composer.json and re-read these notes on each minor bump, since matching and prioritization are core behaviour and a change there can alter which handler runs without any code change on your side.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator