Setting up Projections
PHP Event Sourcing Projections

Setting up Projections

Projections are about deriving current state from the stream of events. Projections can be added in any moment of the application lifetime and be built up from existing history, till the current time. This is powerful concept as it allow for building views quickly and discarding them without pain, when they are not longer needed.
1
#[Projection("inProgressTicketList", Ticket::class] // 1
2
class InProgressTicketList
3
{
4
private Connection $connection;
5
6
public function __construct(Connection $connection)
7
{
8
$this->connection = $connection;
9
}
10
11
#[EventHandler] // 2
12
public function addTicket(TicketWasRegistered $event, array $metadata) : void
13
{
14
$result = $this->connection->executeStatement(<<<SQL
15
INSERT INTO in_progress_tickets VALUES (?,?)
16
SQL, [$event->getTicketId(), $event->getTicketType()]);
17
}
18
19
#[QueryHandler("getInProgressTickets")] // 3
20
public function getTickets() : array
21
{
22
return $this->connection->executeQuery(<<<SQL
23
SELECT * FROM in_progress_tickets
24
SQL)->fetchAllAssociative();
25
}
26
}
Copied!
  1. 1.
    This tells Ecotone that specific class is Projection. The first parameter is the name of the projection and the second is name of the stream (the default is the name of the Aggregate) that this projection subscribes to.
  2. 2.
    Events that this projection subscribes to
  3. 3.
    Optional Query Handlers for this projection. They can be placed in different classes depending on preference.

Document Store

Document Store is a great way to set up your projections. You can freely create DTO objects, or play with simple arrays and Ecotone will serialize/deserialize and store them for you.
1
#[Projection(self::NAME, Account::class)]
2
final class AvailableBalanceProjection
3
{
4
const NAME = "available_balance";
5
const QUERY = "getCurrentBalance";
6
7
public function __construct(private DocumentStore $documentStore) {}
8
9
#[EventHandler]
10
public function whenAccountSetup(AccountSetup $event): void
11
{
12
$this->documentStore->addDocument(
13
self::NAME,
14
$event->accountId,
15
["balance" => 0]
16
);
17
}
18
19
#[EventHandler]
20
public function whenPaymentMade(PaymentMade $event): void
21
{
22
$this->documentStore->updateDocument(
23
self::NAME,
24
$event->accountId,
25
["balance" => $this->getCurrentBalance($event->accountId) + $event->amount]
26
);
27
}
28
29
#[QueryHandler(self::QUERY)]
30
public function getCurrentBalance(UuidInterface $accountId): int
31
{
32
return $this->documentStore->getDocument(self::NAME, $accountId)["balance"];
33
}
34
}
Copied!
For testing purposes you may use In Memory implementation, to speed up your tests or to simple test things out locally.
Export as PDF
Copy link