🧩 Versioned Events
Pillar supports versioned domain events to make schema evolution explicit and safe.
Implement VersionedEvent on your event and declare its current schema version:
use Pillar\Event\VersionedEvent;
final class DocumentCreated implements VersionedEvent
{
public static function version(): int
{
return 2;
}
public function __construct(
public string $title,
public string $created_by
) {}
}- The event’s version is stored in the event store (
event_versioncolumn) alongside its payload. - On load, if a stored event has an older version, Pillar applies registered upcasters step-by-step until the payload reaches the event’s current version.
- You can register multiple upcasters for the same event (e.g. v1→v2, v2→v3). They are applied in ascending order of
fromVersion().
EventContext and metadata
When an event is being upcast or replayed, the payload that your upcaster or handler receives is the serialized body of the event at a given version. Pillar initializes the EventContext from the stored row before upcasting and rehydration, so you can inspect timestamps, correlation IDs, and aggregate IDs without changing the payload itself.
During upcasting or replay you can read:
EventContext::occurredAt()— the original UTC timestamp when the event was recordedEventContext::correlationId()— the correlation id for the logical operationEventContext::aggregateRootId()— the typed aggregate id (when resolvable from the stream), ornullEventContext::isReconstituting()/EventContext::isReplaying()— to distinguish replay from live handling
In most cases, upcasters should remain pure payload transforms (only reshaping the array). When you truly need to branch on metadata (for example, to handle a one-off legacy period differently), prefer using EventContext rather than baking metadata into the payload itself.
Tip: If you refactor an event without changing its shape, you can simply bump the version and register a no-op upcaster for documentation clarity.