Skip to content

Commit 0c29b05

Browse files
authored
Merge pull request #4 from patchlevel/add-command-bus-like-support
Add support for command bus like syntax, Drop PHP 8.1
2 parents c271e53 + 8710a29 commit 0c29b05

File tree

10 files changed

+191
-39
lines changed

10 files changed

+191
-39
lines changed

.github/workflows/unit.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ jobs:
2121
- "lowest"
2222
- "highest"
2323
php-version:
24-
- "8.1"
2524
- "8.2"
2625
- "8.3"
2726
operating-system:

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,48 @@ final class ProfileTest extends AggregateRootTestCase
121121
}
122122
```
123123

124+
### Using Commandbus like syntax
125+
126+
When using the command bus and the `#[Handle]` attributes in your aggregate you can also provide the command directly
127+
for the `when` method.
128+
129+
```php
130+
final class ProfileTest extends AggregateRootTestCase
131+
{
132+
// protected function aggregateClass(): string;
133+
134+
public function testBehaviour(): void
135+
{
136+
$this
137+
->when(new CreateProfile(ProfileId::fromString('1'), Email::fromString('hq@patchlevel.de')))
138+
->then(new ProfileCreated(ProfileId::fromString('1'), Email::fromString('hq@patchlevel.de')));
139+
}
140+
}
141+
```
142+
143+
If more parameters than the command is needed, these can also be provided as additional parameters for `when`. In this
144+
example the we need a string which will be directly passed to the event.
145+
146+
```php
147+
final class ProfileTest extends AggregateRootTestCase
148+
{
149+
// protected function aggregateClass(): string;
150+
151+
public function testBehaviour(): void
152+
{
153+
$this
154+
->given(
155+
new ProfileCreated(
156+
ProfileId::fromString('1'),
157+
Email::fromString('hq@patchlevel.de'),
158+
),
159+
)
160+
->when(new VisitProfile(ProfileId::fromString('2')), 'Extra Parameter / Dependency')
161+
->then(new ProfileVisited(ProfileId::fromString('2'), 'Extra Parameter / Dependency'));
162+
}
163+
}
164+
```
165+
124166
## Testing Subscriber
125167

126168
For testing a subscriber there is a utility class which you can use. Using `SubscriberUtilities` will provide you a

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
}
2222
],
2323
"require": {
24-
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
25-
"patchlevel/event-sourcing": "^3.0.0",
24+
"php": "~8.2.0 || ~8.3.0",
25+
"patchlevel/event-sourcing": "^3.8.0",
2626
"phpunit/phpunit": "^10.1.0||^11.0.0"
2727
},
2828
"require-dev": {

composer.lock

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

src/Test/AggregateRootTestCase.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@
66

77
use Closure;
88
use Patchlevel\EventSourcing\Aggregate\AggregateRoot;
9+
use Patchlevel\EventSourcing\CommandBus\HandlerFinder;
910
use PHPUnit\Framework\Attributes\After;
1011
use PHPUnit\Framework\Attributes\Before;
1112
use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint;
1213
use PHPUnit\Framework\Constraint\ExceptionMessageIsOrContains;
1314
use PHPUnit\Framework\TestCase;
15+
use ReflectionClass;
1416
use Throwable;
1517

1618
abstract class AggregateRootTestCase extends TestCase
1719
{
1820
/** @var array<object> */
1921
private array $givenEvents = [];
20-
private Closure|null $when = null;
22+
private object|null $when = null;
23+
24+
/** @var array<mixed> */
25+
private array $parameters = [];
2126

2227
/** @var array<object> */
2328
private array $expectedEvents = [];
@@ -35,9 +40,11 @@ final public function given(object ...$events): self
3540
return $this;
3641
}
3742

38-
final public function when(Closure $callable): self
43+
/** @param object|Closure $callable */
44+
final public function when(object $callable, mixed ...$parameters): self
3945
{
4046
$this->when = $callable;
47+
$this->parameters = $parameters;
4148

4249
return $this;
4350
}
@@ -78,7 +85,29 @@ final public function assert(): self
7885
}
7986

8087
try {
81-
$return = ($this->when)($aggregate);
88+
$callableOrCommand = $this->when;
89+
$return = null;
90+
91+
if ($callableOrCommand instanceof Closure) {
92+
$return = $callableOrCommand($aggregate);
93+
} else {
94+
foreach (HandlerFinder::findInClass($this->aggregateClass()) as $handler) {
95+
if (!$callableOrCommand instanceof $handler->commandClass) {
96+
continue;
97+
}
98+
99+
$reflection = new ReflectionClass($this->aggregateClass());
100+
$reflectionMethod = $reflection->getMethod($handler->method);
101+
102+
$return = $reflectionMethod->invokeArgs(
103+
$handler->static ? null : $aggregate,
104+
[
105+
$callableOrCommand,
106+
...$this->parameters,
107+
],
108+
);
109+
}
110+
}
82111

83112
if ($aggregate !== null && $return instanceof AggregateRoot) {
84113
throw new AggregateAlreadySet();
@@ -107,6 +136,7 @@ final public function reset(): void
107136
{
108137
$this->givenEvents = [];
109138
$this->when = null;
139+
$this->parameters = [];
110140
$this->expectedEvents = [];
111141
$this->expectedException = null;
112142
$this->expectedExceptionMessage = null;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture;
6+
7+
final readonly class CreateProfile
8+
{
9+
public function __construct(
10+
public ProfileId $id,
11+
public Email $email,
12+
) {
13+
}
14+
}

tests/Unit/Fixture/Profile.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
88
use Patchlevel\EventSourcing\Attribute\Aggregate;
99
use Patchlevel\EventSourcing\Attribute\Apply;
10+
use Patchlevel\EventSourcing\Attribute\Handle;
1011
use Patchlevel\EventSourcing\Attribute\Id;
1112

1213
#[Aggregate('profile')]
@@ -27,17 +28,19 @@ public function email(): Email
2728
return $this->email;
2829
}
2930

30-
public static function createProfile(ProfileId $id, Email $email): self
31+
#[Handle]
32+
public static function createProfile(CreateProfile $createProfile): self
3133
{
3234
$self = new self();
33-
$self->recordThat(new ProfileCreated($id, $email));
35+
$self->recordThat(new ProfileCreated($createProfile->id, $createProfile->email));
3436

3537
return $self;
3638
}
3739

38-
public function visitProfile(ProfileId $profileId): void
40+
#[Handle]
41+
public function visitProfile(VisitProfile $visitProfile, string|null $token = null): void
3942
{
40-
$this->recordThat(new ProfileVisited($profileId));
43+
$this->recordThat(new ProfileVisited($visitProfile->id, $token));
4144
}
4245

4346
public function throwException(): void

tests/Unit/Fixture/ProfileVisited.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class ProfileVisited implements Stringable
1414
public function __construct(
1515
#[IdNormalizer]
1616
public ProfileId $visitorId,
17+
public string|null $token = null,
1718
) {
1819
}
1920

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\PhpUnit\Tests\Unit\Fixture;
6+
7+
final readonly class VisitProfile
8+
{
9+
public function __construct(
10+
public ProfileId $id,
11+
) {
12+
}
13+
}

0 commit comments

Comments
 (0)