PHPackages                             remotelyliving/php-query-bus - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. remotelyliving/php-query-bus

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

remotelyliving/php-query-bus
============================

A php query bus for abstracting querying, data loading, and graph building

1.0.0(5y ago)813[2 PRs](https://github.com/remotelyliving/php-query-bus/pulls)MITPHPPHP &gt;=7.4

Since May 26Pushed 3y ago1 watchersCompare

[ Source](https://github.com/remotelyliving/php-query-bus)[ Packagist](https://packagist.org/packages/remotelyliving/php-query-bus)[ RSS](/packages/remotelyliving-php-query-bus/feed)WikiDiscussions master Synced 5d ago

READMEChangelog (1)Dependencies (11)Versions (4)Used By (0)

[![Build Status](https://camo.githubusercontent.com/76c3c658bbf71f060454ea9c032deadb788522c33603939e48f645df0f8161b2/68747470733a2f2f7472617669732d63692e636f6d2f72656d6f74656c796c6976696e672f7068702d71756572792d6275732e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/remotelyliving/php-query-bus)[![Total Downloads](https://camo.githubusercontent.com/5bdaf2be07acaed3f77380a699d0e131f6cbe53662cb3eab48bc98107086bf2e/68747470733a2f2f706f7365722e707567782e6f72672f72656d6f74656c796c6976696e672f7068702d71756572792d6275732f646f776e6c6f616473)](https://packagist.org/packages/remotelyliving/php-query-bus)[![Coverage Status](https://camo.githubusercontent.com/a85c374d02dd7b01dc90199c3a4c0fc6b814d2e85ce3a04476c3d621aadb23f6/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f72656d6f74656c796c6976696e672f7068702d71756572792d6275732f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/remotelyliving/php-query-bus?branch=master)[![License](https://camo.githubusercontent.com/3ee14e4b8a2e040e00f31a122aab136581040a2be01a5f00e6935e89d5959fb7/68747470733a2f2f706f7365722e707567782e6f72672f72656d6f74656c796c6976696e672f7068702d71756572792d6275732f6c6963656e7365)](https://packagist.org/packages/remotelyliving/php-query-bus)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/7550b5b0b56540d789380868130c0078fd9abb936087ed54810785afda4e8702/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f72656d6f74656c796c6976696e672f7068702d71756572792d6275732f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/remotelyliving/php-query-bus/?branch=master)

php-query-bus: 🚍 A Query Bus Implementation For PHP 🚍
=====================================================

[](#php-query-bus--a-query-bus-implementation-for-php-)

### Use Cases

[](#use-cases)

If you want a light weight compliment to your Command Bus for CQRS, hopefully this library helps out. It's very similar to a Command Bus, but it returns a Result.

I've used magical data loading solutions before, but good old fashioned set of specific Query, Result, and Handler objects for a given Use Case is generally more performant, predictable, and explicit than magic-based implementations.

### Installation

[](#installation)

```
composer require remotelyliving/php-query-bus
```

### Usage

[](#usage)

#### Create the Query Resolver

[](#create-the-query-resolver)

The resolver can have handlers added manually or locate them in a PSR-11 Service Container Queries are mapped 1:1 with a handler and are mapped by the Query class name as the lookup key.

```
$resolver = Resolver::create($serviceContainer) // can locate in service container
    ->pushHandler(GetUserProfileQuery::class, new GetUserProfileHandler()) // can locate in a local map {query => handler}
    ->pushHandlerDeferred(GetUserQuery::class, $lazyCreateMethod); // can locate deferred to save un unnecessary object instantiation
```

#### Create the Query Bus

[](#create-the-query-bus)

The Query Bus takes in a Query Resolver and pushes whatever Middleware you want on the stack.

```
$queryBus = QueryBus::create($resolver)
    ->pushMiddleware($myMiddleware1);

$query = new GetUserProfile('id');
$result = $queryBus->handle($query);
```

Middleware is any callable that returns a Result. Some base middleware is included: [src/Middleware](https://github.com/remotelyliving/php-query-bus/tree/master/src/Middleware)

That's really all there is to it!

### Query

[](#query)

The Query for this library is left intentionally unimplemented. It is just an object. My suggestion for Query objects is to keep them as a DTO of what you need to query your data source by.

An example query might look like this:

```
class GetUserQuery
{
    private bool $shouldIncludeProfile = false;

    private string $userId;

    public function __construct(string $userId)
    {
        $this->userId = $userId;
    }

    public function getUserId(): string
    {
        return $this->userId;
    }

    public function includeProfile(): self
    {
        $this->shouldIncludeProfile = true;
        return $this;
    }
}
```

As you can see, it's just a few getters and option builder.

### Result

[](#result)

The Result is similarly unimplemented except for the provided [AbstractResult](https://github.com/remotelyliving/php-query-bus/blob/master/src/AbstractResult.php). Results can have their own custom getters for your use case. An example Result for the `GetUserQuery` above might look like:

```
class GetUserResult extends AbstractResult implements \JsonSerializable
{
    private User $user;

    private ?UserProfile $userProfile;

    public function __construct(User $user, ?UserProfile $userProfile)
    {
        $this->user = $user;
        $this->userProfileResult = $userProfile;
    }

    public function getUser(): User
    {
        return $this->user;
    }

    public function getUserProfile(): ?UserProfile
    {
        return $this->userProfile;
    }

    public function jsonSerialize(): array
    {
        return [
            'user' => $this->getUser(),
            'profile' => $this->getUserProfile(),
        ];
    }
}
```

As you can see, it's not too hard to start building Result graphs for outputting a response or to feed another part of your app.

### Handler

[](#handler)

The handlers are where the magic happens. Inject what ever repository, API Client, or ORM you need to load data. It will ask the query for query parameters and return a result. You can also request other query results inside a handler from the bus. Going with our GetUserQuery example, a Handler could look like:

```
class GetUserHandler implements Interfaces\Handler
{
    public function handle(object $query, Interfaces\QueryBus $bus): Interfaces\Result
    {
        try {
            $user = $this->userRepository->getUserById($query->getUserId());
        } catch (ConnectectionError $e) {
            // can handle exceptions without blowing up and instead use messaging via
            // AbstractResult::getErrors(): \Throwable[] and AbstractResult::hasErrors(): bool
            return AbstractResult::withErrors($e);
        }

        if (!$user) {
            // can handle nullish cases by returning not found
            return AbstractResult::notFound();
        }

        if (!$query->shouldIncludeProfile()) {
            return new GetUserResult($user, null);
        }

        $profileResult = $bus->handle(new GetUserProfileQuery($query->getUserId()));

        return ($profileResult->isNotFound())
            ? new GetUserResult($user, null)
            : new GetUserResult($user, $profileResult->getUserProfile());
    }
}
```

### Middleware

[](#middleware)

There are a few [Middleware](https://github.com/remotelyliving/php-query-bus/tree/master/src/Middleware) that this library ships with. The default execution order is LIFO and the signature very simple.

A Middleware must return an instance of Result and be callable. That's it!

An example Middleware could be as simple as this:

```
$cachingMiddleware = function (object $query, callable $next) use ($queryCacher) : Interfaces\Result {
    if ($query instanceof Interfaces\CacheableQuery) {
        return $queryCacher->get($query, function () use ($next, $query) { return $next($query); });
    }

    return $next($query);
};
```

#### [QueryCacher](https://github.com/remotelyliving/php-query-bus/blob/master/src/Middleware/QueryCacher.php)

[](#querycacher)

This middleware provides some interesting query caching by utilizing [Probabilistic Early Cache Expiry](https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration)to help prevent cache stampedes. To be cached, a Query must implement the [CacheableQuery](https://github.com/remotelyliving/php-query-bus/blob/master/src/Interfaces/CacheableQuery.php) interface. To recompute cache simply fire off a Query with the value of `CacheableQuery::shouldRecomputeResult()` returning true.

#### [QueryLogger](https://github.com/remotelyliving/php-query-bus/blob/master/src/Middleware/QueryLogger.php)

[](#querylogger)

Helpful for debugging, but best left for dev and stage environments. Looks for the LoggableQuery marker interface on the query

#### [ResultErrorLogger](https://github.com/remotelyliving/php-query-bus/blob/master/src/Middleware/ResultErrorLogger.php)

[](#resulterrorlogger)

Helpful for debugging and alerting based on your logging setup.

#### [PerfBudgetLogger](https://github.com/remotelyliving/php-query-bus/blob/master/src/Middleware/QueryLogger.php)

[](#perfbudgetlogger)

Allows you to set certain rough performance thresholds and log with something has gone over that threshold.

### Future Future Development

[](#future-future-development)

- Result Filtering (should be done at a query level, but would be nice to be able to specify sparse field sets

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity11

Limited adoption so far

Community4

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

2181d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/15091ba12470e0054da94a253c264751ef495c9897c59ea6837e986b31606e63?d=identicon)[remotelyliving](/maintainers/remotelyliving)

---

Tags

querybusgraphquery-bus

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/remotelyliving-php-query-bus/health.svg)

```
[![Health](https://phpackages.com/badges/remotelyliving-php-query-bus/health.svg)](https://phpackages.com/packages/remotelyliving-php-query-bus)
```

###  Alternatives

[ecotone/ecotone

Supporting you in building DDD, CQRS, Event Sourcing applications with ease.

558549.8k17](/packages/ecotone-ecotone)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

728272.9k20](/packages/civicrm-civicrm-core)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[jaxon-php/jaxon-core

Jaxon is an open source PHP library for easily creating Ajax web applications

73142.3k25](/packages/jaxon-php-jaxon-core)[phonetworks/pho-lib-graph

A general purpose graph library in PHP

556.0k1](/packages/phonetworks-pho-lib-graph)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
