Skip to content

Commit fa8d716

Browse files
authored
Merge pull request #11 from shouze/introduce-aggregate-root-and-change-classes
💄 Introduce AggregateRoot & Change classes.
2 parents 68fc86a + c90ace5 commit fa8d716

6 files changed

+115
-30
lines changed

src/Domain/Vehicle.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Shouze\ParkedLife\Domain;
55

6-
class Vehicle
6+
final class Vehicle
77
{
88
private $platenumber;
99

src/Domain/VehicleFleet.php

+12-18
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33

44
namespace Shouze\ParkedLife\Domain;
55

6-
class VehicleFleet
7-
{
8-
private $userId;
6+
use Shouze\ParkedLife\EventSourcing\AggregateRoot;
7+
use Shouze\ParkedLife\EventSourcing\Change;
98

9+
final class VehicleFleet extends AggregateRoot
10+
{
1011
private $vehicles = [];
1112

1213
public function __construct(UserId $userId)
1314
{
14-
$this->userId = $userId;
15+
parent::__construct((string)$userId);
1516
}
1617

1718
public static function ofUser(UserId $userId): VehicleFleet
@@ -21,30 +22,23 @@ public static function ofUser(UserId $userId): VehicleFleet
2122

2223
public function registerVehicle(string $platenumber, string $description)
2324
{
24-
$changes = [
25-
new VehicleWasRegistered($platenumber, (string)$this->userId),
26-
new VehicleWasDescribed($platenumber, $description)
27-
];
28-
29-
foreach ($changes as $change) {
30-
$handler = sprintf('when%s', implode('', array_slice(explode('\\', get_class($change)), -1)));
31-
$this->{$handler}($change);
32-
}
25+
$this->record(new VehicleWasRegistered($this->getAggregateId(), $platenumber));
26+
$this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description));
3327

3428
return $this->vehicleWithPlatenumber($platenumber);
3529
}
3630

37-
public function whenVehicleWasRegistered($change)
31+
public function whenVehicleWasRegistered(VehicleWasRegistered $change)
3832
{
39-
$this->vehicles[] = Vehicle::register($change->getPlatenumber(), new UserId($change->getUserId()));
33+
$this->vehicles[] = Vehicle::register($change->getPlatenumber(), new UserId($change->getAggregateId()));
4034
}
4135

4236
public function describeVehicle(string $platenumber, string $description)
4337
{
44-
$this->whenVehicleWasDescribed(new VehicleWasDescribed($platenumber, $description));
38+
$this->record(new VehicleWasDescribed($this->getAggregateId(), $platenumber, $description));
4539
}
4640

47-
public function whenVehicleWasDescribed($change)
41+
public function whenVehicleWasDescribed(VehicleWasDescribed $change)
4842
{
4943
$vehicle = $this->vehicleWithPlatenumber($change->getPlatenumber());
5044
$vehicle->describe($change->getDescription());
@@ -93,7 +87,7 @@ function ($carry, Vehicle $v) use ($platenumber) {
9387
);
9488

9589
if (null === $vehicle) {
96-
throw new \LogicException(sprintf('Vehicle with plate number %s is unknown in fleet #%s', $platenumber, $this->userId));
90+
throw new \LogicException(sprintf('Vehicle with plate number %s is unknown in fleet #%s', $platenumber, $this->getAggregateId()));
9791
}
9892

9993
return $vehicle;

src/Domain/VehicleWasDescribed.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@
33

44
namespace Shouze\ParkedLife\Domain;
55

6-
final class VehicleWasDescribed
6+
use Shouze\ParkedLife\EventSourcing\Change;
7+
8+
final class VehicleWasDescribed implements Change
79
{
10+
private $userId;
11+
812
private $platenumber;
913

1014
private $description;
1115

12-
public function __construct(string $platenumber, string $description)
16+
public function __construct(string $userId, string $platenumber, string $description)
1317
{
18+
$this->userId = $userId;
1419
$this->platenumber = $platenumber;
1520
$this->description = $description;
1621
}
1722

23+
public function getAggregateId(): string
24+
{
25+
return $this->userId;
26+
}
27+
1828
public function getPlatenumber(): string
1929
{
2030
return $this->platenumber;

src/Domain/VehicleWasRegistered.php

+11-9
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,27 @@
33

44
namespace Shouze\ParkedLife\Domain;
55

6-
final class VehicleWasRegistered
7-
{
8-
private $platenumber;
6+
use Shouze\ParkedLife\EventSourcing\Change;
97

8+
final class VehicleWasRegistered implements Change
9+
{
1010
private $userId;
1111

12-
public function __construct(string $platenumber, string $userId)
12+
private $platenumber;
13+
14+
public function __construct(string $userId, string $platenumber)
1315
{
14-
$this->platenumber = $platenumber;
1516
$this->userId = $userId;
17+
$this->platenumber = $platenumber;
1618
}
1719

18-
public function getPlatenumber(): string
20+
public function getAggregateId(): string
1921
{
20-
return $this->platenumber;
22+
return $this->userId;
2123
}
2224

23-
public function getUserId(): string
25+
public function getPlatenumber(): string
2426
{
25-
return $this->userId;
27+
return $this->platenumber;
2628
}
2729
}

src/EventSourcing/AggregateRoot.php

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Shouze\ParkedLife\EventSourcing;
4+
5+
abstract class AggregateRoot
6+
{
7+
private $aggregateId;
8+
9+
private $recordedChanges = [];
10+
11+
protected function __construct(string $aggregateId)
12+
{
13+
// Use named constructor as it made event sourcing and ubiquitous language easier
14+
$this->aggregateId = $aggregateId;
15+
}
16+
17+
public function getAggregateId(): string
18+
{
19+
return $this->aggregateId;
20+
}
21+
22+
public static function reconstituteFromHistory(\Iterator $history)
23+
{
24+
$aggregateRoot = new static($history->getAggregateId());
25+
26+
foreach ($history as $change) {
27+
$aggregateRoot->apply($change);
28+
}
29+
30+
return $aggregateRoot;
31+
}
32+
33+
public function popRecordedChanges(): \Iterator
34+
{
35+
$pendingChanges = $this->recordedChanges;
36+
37+
$this->recordedChanges = [];
38+
39+
return $pendingChanges;
40+
}
41+
42+
protected function record(Change $change)
43+
{
44+
$this->recordedChanges[] = $change;
45+
$this->apply($change);
46+
}
47+
48+
private function apply(Change $change)
49+
{
50+
$handler = $this->resolveEventHandlerMethodFor($change);
51+
52+
if (false === method_exists($this, $handler)) {
53+
throw new \RuntimeException(sprintf(
54+
"Missing event handler method %s for aggregate root %s",
55+
$handler,
56+
get_class($this)
57+
));
58+
}
59+
60+
$this->{$handler}($change);
61+
}
62+
63+
/**
64+
* For an event named ProductWasRegistered will look for method `whenProductWasRegistered`
65+
*/
66+
private function resolveEventHandlerMethodFor(Change $change)
67+
{
68+
return 'when' . implode('', array_slice(explode('\\', get_class($change)), -1));
69+
}
70+
}

src/EventSourcing/Change.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Shouze\ParkedLife\EventSourcing;
5+
6+
interface Change
7+
{
8+
public function getAggregateId();
9+
}

0 commit comments

Comments
 (0)