<?php
interface DomainEventInterface
{
}
trait EventRecordingTrait
{
/**
* @var array<DomainEventInterface>
*/
protected array $domainEvents = [];
protected function recordThat(DomainEventInterface $domainEvent): void
{
$this->domainEvents[] = $domainEvent;
}
/**
* @return array<DomainEventInterface>
*/
public function releaseEvents(): array
{
$domainEvents = $this->domainEvents;
$this->domainEvents = [];
return $domainEvents;
}
}
readonly class Created implements DomainEventInterface
{
public function __construct(
public string $name,
public string $email
) {
}
}
readonly class NameUpdated implements DomainEventInterface
{
public function __construct(public string $name)
{
}
}
class User
{
use EventRecordingTrait;
// No intention of throwing events, it's just a simple object hydration!
public function __construct(
private string $name,
private string $email,
) {
}
// intent of creating an instance and recording that specific event
public static function create(string $name, string $email): self
{
$self = new User($name, $email);
$self->recordThat(new Created($name, $email));
return $self;
}
// Wouldn't be necessary with some kind of asymetric visibility, but it's not a big hassle
public function getName(): string
{
return $this->name;
}
// Here I would update the same $name attribute but throwing a different event
public function updateName(string $name): void
{
if ($this->name === $name) {
return;
}
$this->recordThat(new NameUpdated($name));
$this->name = $name;
}
// Wouldn't be necessary with some kind of asymetric visibility, but it's not a big hassle
public function getEmail(): string
{
return $this->email;
}
}
$user1 = new User('Hydrated from database', 'user1@example.com');
var_dump($user1);
$user1->updateName('Mutation intent');
var_dump($user1);
$user2 = User::create('Created from controller', 'user2@example.com');
var_dump($user2);
$user2->updateName('Another mutation intent');
var_dump($user2);