Skip to content

Commit 4552334

Browse files
author
chthomas
committed
Initial commit
0 parents  commit 4552334

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5866
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
./vendor
2+
./idea
3+
./coverage

.idea/php-query-bus.iml

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
notifications:
2+
email: false
3+
4+
language: php
5+
6+
php:
7+
- '7.4'
8+
- '7.3'
9+
10+
install:
11+
- composer install
12+
13+
script:
14+
- make
15+
- mkdir -p build/logs
16+
- vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml
17+
18+
after_success:
19+
- travis_retry php vendor/bin/php-coveralls -v

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2017 Christian Thomas <christian.h.thomas@me.com>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
build:
2+
@make dependencies && make dependency-check && make static-analysis && make style-check && make unit-tests && make integration-tests
3+
4+
dependencies:
5+
@composer install
6+
7+
unit-tests:
8+
@vendor/bin/phpunit --bootstrap=./tests/bootstrap.php --testsuite Unit
9+
10+
integration-tests:
11+
@vendor/bin/phpunit --bootstrap=./tests/bootstrap.php --testsuite Integration
12+
13+
test-coverage:
14+
@vendor/bin/phpunit --coverage-html ./coverage
15+
16+
style-check:
17+
@vendor/bin/phpcs --standard=PSR12 ./src/* ./tests/*
18+
19+
dependency-check:
20+
@vendor/bin/composer-require-checker check -vvv ./composer.json
21+
22+
static-analysis:
23+
@vendor/bin/phpstan analyze --level=max ./src && ./vendor/bin/psalm --show-info=false
24+
25+
style-fix:
26+
@vendor/bin/phpcbf --standard=PSR12 ./src ./tests
27+
28+
repl:
29+
@vendor/bin/psysh ./bootstrap/repl.php

README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
[![Build Status](https://travis-ci.org/remotelyliving/php-query-bus.svg?branch=master)](https://travis-ci.org/remotelyliving/php-query-bus)
2+
[![Total Downloads](https://poser.pugx.org/remotelyliving/php-query-bus/downloads)](https://packagist.org/packages/remotelyliving/php-query-bus)
3+
[![Coverage Status](https://coveralls.io/repos/github/remotelyliving/php-query-bus/badge.svg?branch=master)](https://coveralls.io/github/remotelyliving/php-query-bus?branch=master)
4+
[![License](https://poser.pugx.org/remotelyliving/php-query-bus/license)](https://packagist.org/packages/remotelyliving/php-query-bus)
5+
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/remotelyliving/php-query-bus/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/remotelyliving/php-query-bus/?branch=master)
6+
7+
# php-query-bus: A Query Bus Implementation For PHP
8+
9+
### Use Cases
10+
11+
If you want a light weight compliment to your Command Bus for CQRS, hopefully this library helps out.
12+
It's very similar to a Command Bus, but it returns a Result.
13+
14+
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
15+
is generally more performant, predictable, and explicit than array or magic-based implementations.
16+
17+
### Installation
18+
19+
```sh
20+
composer require remotelyliving/php-query-bus
21+
```
22+
23+
### Usage
24+
25+
#### Create the Query Resolver
26+
27+
The resolver can have handlers added manually or locate them in a PSR-11 Service Container
28+
Queries are mapped 1:1 with a handler and are mapped by the Query class name as the lookup key.
29+
```php
30+
$resolver = Resolver::create($serviceContainer) // can locate in service container
31+
->pushHandler(MyQueryHandler1::class, new MyQueryHandler1()) // can locate in a local map
32+
->pushHandlerDeferred(MyQueryHandler2::class, $lazyCreateMethod); // can locate deferred to save un unnecessary object creation
33+
34+
```
35+
36+
#### Create the Query Bus
37+
38+
The Query Bus takes in a Query Resolver and pushes whatever Middleware you want on the stack.
39+
```php
40+
$queryBus = QueryBus::create($resolver)
41+
->pushMiddleware($myMiddleware1);
42+
43+
$query = new MyQuery1('id');
44+
$result = $queryBus->handle($result);
45+
```
46+
47+
That's really all there is to it!
48+
49+
### Query
50+
51+
The DTO's for this library are left intentionally unimplemented. They are just interfaces to implement.
52+
Eventually all magic breaks down somewhere and I'm not providing any here. My suggestion for Query objects
53+
is to keep them as a DTO of what you need to query your data source by.
54+
55+
An example query might look like this:
56+
57+
```php
58+
class GetUserQuery implements Interfaces\Query
59+
{
60+
/**
61+
* @var bool
62+
*/
63+
private $shouldGetProfile = false;
64+
65+
/**
66+
* @var string
67+
*/
68+
private $userId;
69+
70+
public function __construct(string $userId)
71+
{
72+
$this->userId = $userId;
73+
}
74+
75+
public function getUserId(): string
76+
{
77+
return $this->userId;
78+
}
79+
80+
public function addUserProfile(): self
81+
{
82+
$this->shouldGetProfile = true;
83+
return $this;
84+
}
85+
86+
public function getGetUserProfileQuery(): ?GetUserProfileQuery
87+
{
88+
return ($this->shouldGetProfile)
89+
? new GetUserProfileQuery($this->userId)
90+
: null;
91+
}
92+
}
93+
```
94+
95+
As you can see, it's just a few getters and option builder.
96+
97+
### Result
98+
99+
The Result is similarly unimplemented.
100+
Results must implement `\JsonSerializable` but that's about it.
101+
They can have their own custom getters for your use case. An example Result for the `GetUserQuery` above might look like:
102+
103+
```php
104+
class GetUserResult implements Result
105+
{
106+
107+
/**
108+
* @var \stdClass|null
109+
*/
110+
private $user;
111+
112+
/**
113+
* @var \RemotelyLiving\PHPQueryBus\Tests\Stubs\GetUserProfileResult|null
114+
*/
115+
private $userProfileResult;
116+
117+
public function __construct(?\stdClass $user, GetUserProfileResult $userProfileResult = null)
118+
{
119+
$this->user = $user;
120+
$this->userProfileResult = $userProfileResult;
121+
}
122+
123+
public function getUser(): ?\stdClass
124+
{
125+
return $this->user;
126+
}
127+
128+
public function getUserProfileResult(): ?GetUserProfileResult
129+
{
130+
return $this->userProfileResult;
131+
}
132+
133+
public function jsonSerialize(): array
134+
{
135+
return [
136+
'user' => $this->getUser(),
137+
'profile' => $this->getUserProfileResult(),
138+
];
139+
}
140+
}
141+
```
142+
143+
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.
144+
145+
### Handler
146+
147+
The handlers are where the magic happens. Inject what ever repository or ORM you need to load data.
148+
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.
149+
Going with our GetUserQuery example, a Handler could look like:
150+
151+
```php
152+
class GetUserHandler implements Handler
153+
{
154+
public function handle(Query $query, QueryBus $bus): Result
155+
{
156+
$user = $this->userRepository->getUserById($query->getUserId());
157+
158+
return ($query->getGetUserProfileQuery())
159+
? new GetUserResult($user, $bus->handle($query->getGetUserProfileQuery()))
160+
: new GetUserResult($user);
161+
}
162+
}
163+
```
164+
165+
### Middleware
166+
167+
There are a few middleware that this library ships with. Take a look and see if any are worth pushing on to the stack.
168+
The default execution order is LIFO and the signature very simple.
169+
170+
A Middleware must return an instance of Result and be callable. That's it!
171+
172+
An example Middleware could be as simple as this:
173+
174+
```php
175+
$cachingMiddleware = function (Query $query, callable $next) use ($queryCacher) : Result {
176+
if ($query instanceof CacheableQuery) {
177+
return $queryCacher->get($query, function () use ($next, $query) { return $next($query); });
178+
}
179+
180+
return $next($query);
181+
};
182+
```
183+
184+
### Future Future Development
185+
186+
- Result Filtering (should be done at a query level, but would be nice to be able to specify sparse field sets

composer.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "remotelyliving/php-query-bus",
3+
"description": "A php library for abstracting querying, data loading, and graph building",
4+
"keywords": ["query bus", "query", "bus", "graph"],
5+
"type": "library",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "chthomas",
10+
"email": "christian.h.thomas@me.com"
11+
}
12+
],
13+
"minimum-stability": "stable",
14+
"require": {
15+
"php": ">=7.3",
16+
"ext-json": "*",
17+
"ext-filter": "*",
18+
"ext-mbstring": "*",
19+
"psr/log": "^1.0",
20+
"symfony/event-dispatcher": "^5.0",
21+
"psr/container": "^1.0",
22+
"myclabs/php-enum": "^1.7"
23+
},
24+
"require-dev": {
25+
"phpunit/phpunit": "^8.0",
26+
"phpstan/phpstan": "^0.11.19",
27+
"maglnet/composer-require-checker": "^2.0",
28+
"squizlabs/php_codesniffer": "^3.3",
29+
"php-coveralls/php-coveralls": "^2.2",
30+
"vimeo/psalm": "^3.7"
31+
},
32+
"autoload": {
33+
"psr-4": {
34+
"RemotelyLiving\\PHPQueryBus\\" : "src/"
35+
}
36+
},
37+
"autoload-dev": {
38+
"psr-4": {
39+
"RemotelyLiving\\PHPQueryBus\\Tests\\" : "tests/"
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)