A lightweight and modern Event Sourcing library for PHP 8.3+
- Stores domain events instead of the current state
- Rebuilds aggregate state through event replay
- Supports projections for building read models
- Provides interfaces for custom event stores and dispatchers
- Resolver — module for generating differences between old and new state.
- Restorer — module for rebuilding an object from changes.
- Merger — service for step-by-step application of changes to an object.
- ContextDTO — DTO for passing context during change analysis (path, collection type, attributes).
- Attributes — PHP 8.3 attributes for controlling change detection behavior.
composer require ufo-tech/event-sourcing| Class | Description |
|---|---|
MainResolver |
Delegates change detection to appropriate resolver |
ObjectResolver |
Handles objects via Reflection API |
CollectionResolver |
Supports associative arrays |
ArrayResolver |
Handles indexed arrays |
ScalarResolver |
Compares scalar values |
| Class | Description |
|---|---|
Merger |
Merges changes into the base state |
ObjectRestorer |
Rebuilds an object from a collection of changes via DTOTransformer |
ObjectDefinition |
Defines the object type to restore from change history |
| Interface | Purpose |
|---|---|
ResolverInterface |
Detects changes between states |
MergerInterface |
Merges changes into a state |
RestorerInterface |
Restores an object from change history |
MainResolverInterface |
Encapsulates multiple resolvers |
MainResolverFactoryInterface |
Factory for creating the main resolver |
| Attribute | Purpose |
|---|---|
#[ChangeIgnore] |
Ignores a field during change detection |
#[AsCollection] |
Marks a field as a key-based collection |
ContextDTO allows passing additional metadata during resolving:
- Current path (
path) - Type (collection/array/scalar)
- Special delete placeholder (
__DELETED__) - Supports nested paths for objects and collections
ContextDTO::create()creates a base context with root pathroot.forPath(string $param)adds a path segment (e.g.,root.products).makeAssocByPath(string $path)marks a path as associative (e.g.,products,root.products.*).- Supports pattern paths with
$that converts to*for nested collections. - Delete placeholder can be customized via
ContextDTO::create(deletePlaceholder: '__DELETED__').
use Ufo\EventSourcing\Resolver\ContextDTO;
// Single path
$context = new ContextDTO(assocPaths: ['products']);
$context = new ContextDTO(assocPaths: 'products');
// Multiple paths
$context = new ContextDTO(assocPaths: ['products', 'items.subitems']);
// Custom delete placeholder
$context = new ContextDTO(deletePlaceholder: '__DELETED__', assocPaths: ['products']);// All subitems inside any items are treated as collections
$context = ContextDTO::create()
->makeAssocByPath('items.$.subitems');
// products.*.discounts.*.details are treated as associative
$context = ContextDTO::create()
->makeAssocByPath('products.$.discounts.$.details');
// Root-level collection
$context = ContextDTO::create(assocPaths: ['']);📝 Note: $ automatically converts to * for wildcard path matching.
$mainResolver = (new DefaultResolverFactory())->create();
$diff = $mainResolver->resolve($oldObject, $newObject);$context = ContextDTO::create()->makeAssocByPath('products');
$diff = $mainResolver->resolve($old, $new, $context);$restorer = new ObjectRestorer(new Merger());
$restored = $restorer->restore(
(new ObjectDefinition(MyDto::class))
->addChanges($firstChange)
->addChanges($secondChange)
);- Event Sourcing and CQRS architectures
- Domain-Driven Design (DDD) based projects
- Service-oriented systems with event-based persistence
Full documentation available at docs.ufo-tech.space
MIT © UFO Tech