For the complete documentation index, see llms.txt. This page is also available as Markdown.

Projection Introduction

PHP Event Sourcing Projections

The Problem

Once you start storing storing events instead of updating rows — you will quickly find out your users still need a ticket list page, a dashboard, a report. How do you turn a stream of "what happened" into a table you can query?

In traditional applications, when a ticket is created you run an INSERT, when it's closed you run an UPDATE. The database always holds the current state. But with Event Sourcing, you store what happenedTicketWasRegistered, TicketWasClosed — as an append-only log of events.

Think of it like a bank account: instead of storing "balance = 500", you store every deposit and withdrawal. The balance is derived by replaying the history.

But your users don't want to replay history every time they load a page. They need a ready-to-query table. That's what Projections do.

Demo and Materials

You will find extensive article series on Ecotone's Projection System on the blog. This will help understand essentials from the very beginning up to running Projections in production:

Runnable demo implementations:

What is a Projection?

A Projection reads events from an Event Stream (the append-only log) and builds a read-optimized view from them — a database table, a document, a cache entry. Think of it as a materialized view built from events.

The views built by Projections are called Read Models. They exist only for reading and can be rebuilt at any time from the Event Stream.

The Git Mental Model

If you are new to projections, this analogy carries you a long way:

Git
Event Sourcing

Commit history

Event Stream

Working directory

Read Model

git checkout

Projection running

A second clone of the same repo

A second Read Model from the same events

git reset --hard

#[ProjectionReset]

The Event Stream is the authoritative history. Read Models are views of that history — you can have many of them, each one optimized for a different question (a ticket list, a counter, a search index). Throwing one away never destroys data, because the history is still there. Building a second one from scratch is just replaying the commits into a new working directory.

This is also why projections are safe to evolve aggressively. When you fix a bug in a Read Model, you have not corrupted any "truth" — only a view of it. Rebuild the view from the events and it is correct again.

Events stored in the Event Stream

From these events, we want to build a list of all tickets with their current status:

Read Model: list of tickets with current status

Building Your First Projection

Let's say we have a Ticket Event Sourced Aggregate that produces two events — TicketWasRegistered and TicketWasClosed. We want to build a read model table showing all in-progress tickets.

That's all you need. Let's break down what each part does:

  1. #[ProjectionV2('ticket_list')] — marks this class as a Projection with name ticket_list

  2. #[FromAggregateStream(Ticket::class)] — tells the Projection to read events from the Ticket aggregate's stream

  3. #[ProjectionInitialization] — called when the Projection is first set up (creates the table)

  4. #[EventHandler] — subscribes to specific event types. Ecotone routes events by the type-hint.

  5. #[ProjectionDelete] and #[ProjectionReset] — called when the projection is deleted or reset

There is no additional configuration needed. Ecotone takes care of delivering events, initializing, and triggering the Projection.

Position Tracking

Each Projection remembers where it left off in the Event Stream — like a bookmark in a book. When a new event triggers the Projection, it fetches only the events after its last position.

This means:

  • New Projections start from the beginning of the stream and catch up to the present

  • Existing Projections only process new events they haven't seen yet

  • After a failure, the Projection resumes from its last successfully committed position

This is what makes it possible to deploy a new Projection at any point in time and have it automatically build up from the full event history.

Feature Overview

Ecotone Projections come in two editions. The open-source edition covers the full projection lifecycle for globally tracked projections. Enterprise adds scaling, advanced operations, and deployment strategies.

Feature
Open Source
Enterprise

Global (non-partitioned) projection

Yes

Yes

Yes

Yes

Yes

Yes

Lifecycle management (init, delete, reset, trigger)

Yes

Yes

Multiple event streams

Yes

Yes

Projection state

Yes

Yes

Event emission (EventStreamEmitter)

Yes

Yes

Sync backfill

Yes

Yes

Batch size configuration

Yes

Yes

Gap detection

Yes

Yes

Yes

Yes

Polling execution

Yes

Partitioned projections

Yes

Streaming projections (Kafka, RabbitMQ)

Yes

Async backfill (parallel workers for partitioned)

Yes

Rebuild (sync and async with parallel workers)

Yes

Blue-green deployments

Yes

Yes

Multi-tenant projections

Yes

Custom extensions (StreamSource, StateStorage, PartitionProvider)

Yes

What's Next

Last updated

Was this helpful?