patchlevel/event-sourcing
70.0
Strong · 22 September 2026
14.1k
lines of production code
PHP
primary language
7
measurements over time
What this system is
This system is a PHP-based event sourcing library that manages domain state by recording and replaying events, supporting both command and query operations through dedicated buses. It provides infrastructure for persisting events and snapshots via Doctrine DBAL, handling schema migrations, encryption, and stream splitting. The library also includes a subscription engine for processing events asynchronously, along with CLI tools for database and schema management.
How it got here
2020–2022 — v3.0 API overhaul and modernization
44 changes.
This period centered on a major v3.0 release that overhauled the core API to use PHP 8.1+ attributes and modern tooling, replacing legacy class-based definitions with a metadata-driven approach. The architecture was significantly refactored to support PSR standards, including PSR-14 event dispatching, PSR-20 clock interfaces, and Doctrine DBAL 4.x, while introducing new features like snapshot stores, query buses, and stream splitting. Comprehensive testing and CLI tooling were added to support the new serialization, upcasting, and schema management capabilities.
2023–2024 — Subscription engine and message abstraction overhaul
35 changes.
This period focused on restructuring the subscription engine with robust retry strategies, gap detection, and metadata-driven subscriber resolution. It also introduced a comprehensive message abstraction layer featuring typed headers, translators, and a fluent criteria system for precise event store queries. Extensive integration and unit tests were added to validate these new components alongside personal data encryption and child aggregate support.
2025–2026 — CommandBus and Subscription enhancements
9 changes.
This period focused on implementing a new CommandBus with aggregate and service handler support, alongside an immutable subscription lookup API for filtering event streams. It also introduced subscription cleanup mechanisms for dropping database artifacts and expanded test coverage to include architecture integrity checks and integration benchmarks.
Features
Added Doctrine-based cipher key storage implementations
Two new classes, DoctrineCipherKeyStore and ExtensionDoctrineCipherKeyStore, have been added to the Cryptography component to persist encryption keys in a database via Doctrine DBAL. DoctrineCipherKeyStore provides a basic key store backed by a 'crypto\_keys' table, while ExtensionDoctrineCipherKeyStore offers a more advanced store with a 'cryptography\_keys' table that includes creation timestamps and supports retrieving the current key for a subject. Both classes implement the CipherKeyStore interface and automatically configure their respective database schemas when used with the event sourcing schema configurator.
src/Cryptography · high confidence
Added scripts to extract and inject PHP code in documentation
Two new CLI tools, \bin/docs-extract-php-code\ and \bin/docs-inject-php-code\, have been added to manage PHP code snippets within Markdown documentation. The extraction script scans Markdown files in the \docs\ directory, extracts fenced PHP code blocks, and writes them as separate \.php\ files into a \docs\_php\ directory. The injection script reads these extracted PHP files, replaces the corresponding code blocks in the original Markdown files with the live code content, and preserves frontmatter, effectively keeping the documentation's code examples synchronized with the extracted source.
bin · high confidence
Automatic normalization of AggregateRootId types
A new IdNormalizer has been introduced to handle serialization and deserialization of AggregateRootId instances. This normalizer automatically detects the specific AggregateRootId class via reflection when no explicit class is provided, allowing the system to seamlessly convert aggregate IDs to and from strings during data hydration without requiring manual configuration for each aggregate type.
src/Serializer/Normalizer · high confidence
Immutable subscription lookup API for filtering event stream messages
The Subscription/Lookup component now provides an immutable builder pattern for querying event store messages. Users can construct specific lookup criteria by chaining methods such as \events()\, \stream()\, \aggregateName()\, and \aggregateId()\, with support for filtering by the current message's context via \currentStream()\ and \currentAggregate()\. The direction of the search can be controlled with \forward()\ or \backwards()\, and results are retrieved using \fetchAll()\, \fetchFirst()\, or \fetchLast()\. If no matching message is found, a new \MessageNotFound\ exception is thrown.
src/Subscription/Lookup · high confidence
Introduce command-to-aggregate handlers with automatic parameter resolution
The CommandBus handler layer now includes \CreateAggregateHandler\ and \UpdateAggregateHandler\ to automatically map commands to aggregate roots. These handlers use a new \ParameterResolver\ interface and its \DefaultParameterResolver\ implementation to inject dependencies into aggregate methods, supporting both explicit \Inject\ attributes and type-based resolution via a PSR-11 container. The \UpdateAggregateHandler\ specifically locates the target aggregate by reading the \Id\ attribute on the command, throwing a new \AggregateIdNotFound\ exception if the identifier is missing, and ensures type safety with \ServiceNotResolvable\ exceptions for configuration errors.
src/CommandBus/Handler · high confidence
Introduce metadata namespace with ClassFinder and base exception
The src/Metadata directory now contains the new ClassFinder utility, which scans specified paths for PHP files and returns a sorted list of class names found within them, alongside a new abstract MetadataException class that extends RuntimeException to serve as a base for metadata-related errors.
src/Metadata · high confidence
Introduce new CommandBus implementation with aggregate and service handler support
The \src/CommandBus\ directory now contains a complete, new CommandBus implementation. This includes the \SyncCommandBus\ for synchronous dispatch, an \InstantRetryCommandBus\ that automatically retries commands on specific exceptions (defaulting to \AggregateOutdated\), and a handler resolution system supporting both aggregate root methods (via \AggregateHandlerProvider\) and injected services (via \ServiceHandlerProvider\). The system uses \HandlerFinder\ to locate methods marked with the \Handle\ attribute, supporting union types and inheritance for command matching, and enforces that only one handler is found per command, throwing \MultipleHandlersFound\ or \HandlerNotFound\ as appropriate.
src/CommandBus · high confidence
Introduce new event serialization API with upcasting and encryption support
The \src/Serializer\ directory now provides a new event serialization API, introducing the \EventSerializer\ interface and its \DefaultEventSerializer\ implementation. This change allows users to serialize and deserialize domain events using a configurable hydrator and encoder (defaulting to JSON). A key behavioral addition is the support for an optional \Upcaster\, which enables transforming event payloads and names during deserialization to handle schema evolution. Additionally, the serializer factory method \createFromPaths\ supports injecting a \PayloadCryptographer\ to encrypt sensitive data within event snapshots.
src/Serializer · high confidence
Introduce snapshot store with adapter-based persistence and versioning
The snapshot subsystem now provides a configurable store that saves and loads aggregate root states using pluggable adapters. The DefaultSnapshotStore resolves the correct adapter via metadata, hydrates aggregate data using the hydrator library, and enforces a snapshot version check to ensure compatibility when loading. It also supports payload encryption via PayloadCryptographer and includes specific exception types (AdapterNotFound, SnapshotNotFound, SnapshotNotConfigured, SnapshotVersionInvalid) to handle configuration and retrieval errors.
src/Snapshot · high confidence
Introduce structured event store criteria and builder
This change introduces a new, structured criteria system for querying the event store, replacing ad-hoc filtering with a dedicated \Criteria\ object and a fluent \CriteriaBuilder\. Users can now filter events by stream name (including wildcard support via \StreamCriterion::startWith\), aggregate name and ID, event IDs, index ranges, playhead ranges, and archived status. The \CriteriaBuilder\ simplifies constructing these queries, while individual criterion classes (e.g., \AggregateIdCriterion\, \ArchivedCriterion\) provide type-safe constraints. This enables more precise and maintainable event retrieval logic within the store layer.
src/Store/Criteria · high confidence
Introduce subscription persistence and in-memory store implementations
Added a new subscription store layer under src/Subscription/Store, providing a Doctrine-based persistence implementation (DoctrineSubscriptionStore) and an in-memory alternative (InMemorySubscriptionStore) that supports clearing stored subscriptions. The change introduces a SubscriptionStore interface with get, find, add, update, and remove operations, along with a LockableSubscriptionStore interface for transactional locking support. It also includes supporting types such as SubscriptionCriteria for filtering, exception classes (SubscriptionNotFound, SubscriptionAlreadyExists, TransactionCommitNotPossible), and ensures last saved timestamps are updated on add and update operations.
src/Subscription/Store · high confidence
Introduce upcasting mechanism for event serialization
Added new classes in the \src/Serializer/Upcast\ directory to support upcasting events during deserialization. The \Upcast\ class acts as an immutable value object holding the event name and payload, providing methods to replace the event name, the entire payload, or specific keys within the payload. The \Upcaster\ interface defines a callable contract for transforming these \Upcast\ objects, and the \UpcasterChain\ class allows multiple upcasters to be composed and executed sequentially, enabling users to modify event data or rename events as they are loaded from the store.
src/Serializer/Upcast · high confidence
Introduction of a synchronous Query Bus with attribute-based handler discovery
This change introduces a new Query Bus component to the event-sourcing library, enabling read-model queries alongside existing command handling. The implementation includes a \SyncQueryBus\ that dispatches queries to single handlers, supported by a \ServiceHandlerProvider\ that automatically discovers query handlers via the new \\#\[Answer\]\ attribute on methods. The \HandlerFinder\ inspects method signatures to map queries to handlers, while \ChainHandlerProvider\ allows combining multiple handler sources. This adds a dedicated read path for the application, distinct from the existing command bus infrastructure.
src/Attribute · high confidence
New CLI commands for database, schema, event inspection, and subscription management
This release introduces a comprehensive set of new console commands for managing the event-sourcing infrastructure. Database lifecycle is now handled via \event-sourcing:database:create\ and \event-sourcing:database:drop\, while schema management is covered by \event-sourcing:schema:create\, \event-sourcing:schema:update\, and \event-sourcing:schema:drop\, all supporting a \--dry-run\ option to preview SQL changes. Event data can be inspected using \event-sourcing:show\ (with pagination, limits, and stream filtering) and \event-sourcing:show-aggregate\ (interactive or by ID). A new \event-sourcing:debug\ command displays registered aggregates, events, and subscribers. Subscription lifecycle is fully exposed through commands for booting (\event-sourcing:subscription:boot\), running (\event-sourcing:subscription:run\), pausing, reactivating, refreshing, removing, setting up, tearing down, and checking status. Additionally, \event-sourcing:store:migrate\ allows migrating events between stores with buffering, and \event-sourcing:watch\ provides a live stream of events with filtering options.
src/Console/Command · high confidence
New Doctrine schema management components
The src/Schema directory now includes a suite of new classes for managing Doctrine database schemas: ChainDoctrineSchemaConfigurator for chaining multiple configurators, DoctrineHelper for checking database compatibility, DoctrineMigrationSchemaProvider for integrating with Doctrine Migrations, DoctrineSchemaDirector for executing create/update/drop operations (with dry-run support via DryRunSchemaDirector), and both DoctrineSchemaListener (new) and the deprecated DoctrineSchemaSubscriber for hooking into ORM schema generation events.
src/Schema · high confidence
New JSON encoder and exception types for serialization errors
The serializer now includes a dedicated JSON encoder (JsonEncoder) that implements the new Encoder interface, supporting a pretty-print option and throwing specific exceptions (EncodeNotPossible, DecodeNotPossible) on failure. This provides clearer error handling for JSON serialization and deserialization operations within the event sourcing system.
src/Serializer/Encoder · high confidence
New argument resolvers for subscription subscribers
Subscription subscribers can now automatically resolve method arguments for the message, event, aggregate ID, recorded timestamp, and a lookup helper. This is achieved through a new ArgumentResolver interface and several implementations (MessageArgumentResolver, EventArgumentResolver, AggregateIdArgumentResolver, RecordedOnArgumentResolver, and LookupResolver), allowing subscribers to type-hint these values directly in their handler methods instead of extracting them manually from the message.
src/Subscription/Subscriber/ArgumentResolver · high confidence
New console helper classes for database management and input validation
This change introduces three new classes in the Console namespace to support CLI operations. DoctrineHelper provides utilities for managing database connections, including extracting database names, copying connections without database context, and creating or dropping databases. InputHelper offers static methods for validating and casting input values (strings, integers, booleans, and lists) with specific type constraints, throwing InvalidArgumentGiven exceptions on failure. OutputStyle extends SymfonyStyle to provide specialized formatting for event messages, displaying stream details, headers, and payloads in a structured table format.
src/Console · high confidence
New event metadata and registry system with caching and stream splitting
This change introduces a new event metadata and registry system in the \src/Metadata/Event\ directory. It adds an \EventRegistry\ and corresponding factories (\AttributeEventRegistryFactory\, \Psr6EventRegistryFactory\, \Psr16EventRegistryFactory\) to manage event class-to-name mappings, including support for event aliases. A new \EventMetadata\ structure and factory (\AttributeEventMetadataFactory\, \Psr6EventMetadataFactory\, \Psr16EventMetadataFactory\) expose event details such as name, aliases, and a \splitStream\ flag. The \splitStream\ flag, detected via the \SplitStream\ attribute, allows the system to archive past events when a marked event is saved, requiring the marked event to hold all data needed to recreate the aggregate state. The implementation also includes comprehensive caching support for both metadata and registry via PSR-6 and PSR-16 interfaces, and introduces several new exception classes for better error handling during metadata resolution.
src/Metadata/Event · high confidence
New message abstraction with headers, translation, and reduction capabilities
The \src/Message\ area now introduces a new \Message\ class that wraps an event and supports typed headers, allowing users to attach and retrieve metadata alongside the core event data. To handle serialization scenarios where header classes may be missing or removed, a \MissingHeaders\ class is provided to preserve raw header data without loss. The area also adds a \Pipe\ class for chaining message translators to process message streams, and a \Reducer\ class that enables stateful reduction of message sequences using specific or generic handlers, providing a functional approach to aggregating event data.
src/Message · high confidence
New message header registry and factory for automatic internal header loading
The \src/Metadata/Message\ area now introduces a \MessageHeaderRegistry\ and \MessageHeaderRegistryFactory\ interface to manage message headers. The registry automatically includes internal headers (such as \streamName\, \playhead\, \recordedOn\, \aggregate\, \archived\, \newStreamStart\, \eventId\, and \index\) alongside any user-defined headers, ensuring these system-level metadata fields are always available. An \AttributeMessageHeaderRegistryFactory\ implementation is provided to scan class paths and register headers based on the \Header\ attribute, simplifying the setup process for users who rely on attribute-based configuration.
src/Metadata/Message · high confidence
New message translator components for event filtering and header manipulation
The \src/Message/Translator\ directory now contains a suite of new translator classes that allow users to filter, transform, and manage event messages before they are processed. This includes \AggregateToStreamHeaderTranslator\ to convert aggregate headers into stream-specific headers, \ChainTranslator\ to execute multiple translators in sequence, and various filtering translators like \IncludeEventTranslator\, \ExcludeEventTranslator\, \FilterEventTranslator\, and \UntilEventTranslator\ to control which events pass through based on class type, headers, or timestamps. Additionally, \ReplaceEventTranslator\ enables event substitution, \RecalculatePlayheadTranslator\ handles playhead index updates, and \ClosureMiddleware\ allows for custom logic injection.
src/Message/Translator · high confidence
New snapshot adapter infrastructure with in-memory and PSR cache implementations
The snapshot subsystem now includes a new \SnapshotAdapter\ interface and three concrete implementations: an \InMemorySnapshotAdapter\ for testing or ephemeral use, a \Psr16SnapshotAdapter\ wrapping PSR-16 simple cache, and a \Psr6SnapshotAdapter\ wrapping PSR-6 cache item pools. A \SnapshotNotFound\ exception is introduced to signal missing snapshots. This change provides the foundational storage abstraction for snapshot operations, allowing users to choose between in-memory and standard PSR-compliant cache backends for persisting event-sourced snapshots.
src/Snapshot/Adapter · high confidence
Subscriber metadata reflection and caching infrastructure
The \src/Metadata/Subscriber\ location now provides the core infrastructure for reflecting subscriber configuration via attributes. This includes the \AttributeSubscriberMetadataFactory\ which parses \Subscriber\, \Subscribe\, \Setup\, \Teardown\, \Cleanup\, and \OnFailed\ attributes, along with strict validation to prevent duplicate method definitions and mixed \teardown\/\cleanup\ usage. The package introduces \ArgumentMetadata\ to capture parameter types and nullability, and adds \Psr6SubscriberMetadataFactory\ and \Psr16SubscriberMetadataFactory\ to enable caching of the reflection results for improved performance.
src/Metadata/Subscriber · high confidence
Subscription cleanup feature for dropping database tables and indexes
Added a new subscription cleanup mechanism that allows subscriptions to define tasks for removing database artifacts (tables and indexes) upon completion. This includes a \Cleaner\ interface and a \DefaultCleaner\ implementation that dispatches cleanup tasks to registered handlers. A specific \DbalCleanupTaskHandler\ is provided to execute \DropTableTask\ and \DropIndexTask\ operations, supporting both single connections and a connection registry for multi-connection environments. The feature includes specific exception types like \CleanupFailed\, \NoHandlerForCleanupTask\, and \ConnectionNameNotSupported\ to handle errors during the cleanup process.
src/Subscription/Cleanup · high confidence
Removals
Removal of legacy projection and event stream components
The legacy \EventStream\ class, which managed event dispatching order, and the \Projection\ interface along with its associated \ProjectionRepository\ and \ProjectionException\ have been removed from the codebase. This deletion eliminates the previous mechanism for handling projections via the \MessageSubscriberInterface\ and the specific repository logic that iterated through projections to handle \AggregateChanged\ events, indicating a structural shift in how projections are managed within the event sourcing system.
src/Projection · high confidence
Behavioural changes
Aggregate root interface and behavior refactored to use traits and metadata
The AggregateRoot class has been converted from an abstract class to an interface, shifting the implementation details into reusable traits (AggregateRootBehaviour, AggregateRootAttributeBehaviour) and a new BasicAggregateRoot base class. This change introduces a metadata-driven approach for applying events, allowing for more flexible configuration via attributes and supporting child aggregates. The aggregate ID is now represented by the AggregateRootId interface instead of a string, and the old AggregateChanged base class for events has been removed in favor of plain objects.
src/Aggregate · high confidence
Event store API refactored to use Message-based streams and criteria
The event store interface has been rewritten to replace the old aggregate-centric methods (load by aggregate/id, save aggregate events) with a unified, criteria-based approach. The Store interface now exposes load() returning a Stream of Message objects, supporting limit, offset, and backwards iteration, alongside a count() method. Saving is now done via save(Message ...$messages), and the implementation has shifted from the old MysqlSingleTableStore to new DoctrineDbalStore and StreamDoctrineDbalStore classes that handle message serialization, header management (Index, EventId, Playhead, RecordedOn, StreamName), and database-specific locking. A new InMemoryStore and ReadOnlyStore wrappers are also introduced, along with a comprehensive set of header classes and store-specific exceptions.
src/Store · high confidence
EventBus refactored to support PSR-14 and attribute-based listeners
The event bus implementation has been restructured to support PSR-14 event dispatching via the new Psr14EventBus, which delegates to a PSR-14 EventDispatcherInterface. Additionally, a new attribute-based listener system has been introduced, allowing listeners to be automatically discovered via the Subscribe attribute and processed by the AttributeListenerProvider. The DefaultEventBus now uses a Consumer pattern with DefaultConsumer to handle message consumption, providing a more modular and extensible event handling mechanism.
src/EventBus · high confidence
Introduce configurable header serialization with graceful handling of missing headers
The message serialization layer now includes a dedicated \HeadersSerializer\ interface and a \DefaultHeadersSerializer\ implementation that manages message headers separately from the main payload. A key behavioral change is the introduction of graceful handling for unknown or missing headers during deserialization: users can now configure a list of specific header names or use a wildcard (\\*\) to allow the system to silently ignore headers it does not recognize, rather than throwing an error. This is supported by a new \MissingHeaders\ collection class that preserves unrecognized header data for downstream processing.
src/Message/Serializer · high confidence
Major v3.0 API overhaul and migration to PHP 8.1+ with modern tooling
This release introduces a significant behavioral change to the core API, requiring users to migrate from the previous class-based event and aggregate definitions to a modern attribute-driven approach (e.g., using \\#\[Event\]\ and \\#\[Aggregate\]\ instead of extending \AggregateChanged\). The event store schema has changed, renaming database columns to snake\_case (\aggregate\_id\, \recorded\_on\) and storing event names instead of FQCNs, necessitating a database migration and potential upcasting. Additionally, the library now requires PHP 8.1 or higher, has dropped support for the Psalm static analyzer in favor of PHPStan, and restructures the test suite into distinct unit and integration suites.
(repo-wide) · high confidence
New aggregate root metadata registry and factory system
The system now uses a dedicated \AggregateRootRegistry\ to map aggregate names to classes, replacing the previous implicit lookup. This registry is built by new factory classes (\AttributeAggregateRootRegistryFactory\, \Psr6AggregateRootRegistryFactory\, \Psr16AggregateRootRegistryFactory\) that scan code paths for \\#\[Aggregate\]\ attributes and optionally cache the result. Metadata for each aggregate is now provided by \AggregateRootMetadata\ objects, populated by factories like \AttributeAggregateRootMetadataFactory\ which read attributes such as \\#\[Id\]\, \\#\[Apply\]\, \\#\[Snapshot\]\, and \\#\[Stream\]\. The change introduces specific exceptions (e.g., \AggregateRootAlreadyInRegistry\, \AggregateRootIdNotFound\) to enforce strict metadata validation during registration.
src/Metadata/AggregateRoot · high confidence
Refactored subscriber access with metadata-driven resolution and new lifecycle hooks
The subscription engine's subscriber handling has been refactored to use a metadata-driven approach via the new MetadataSubscriberAccessor and MetadataSubscriberAccessorRepository, replacing the previous accessor interfaces. This change introduces support for argument resolvers to inject context (like Message, Event, or AggregateId) into subscriber methods, enforces unique subscriber IDs by throwing DuplicateSubscriberId on conflicts, and adds support for batch processing via the BatchableSubscriber interface. Additionally, new lifecycle hooks for cleanup and failure handling are now accessible through the metadata, while legacy helpers (SubscriberHelper, SubscriberUtil) and the experimental trace feature have been deprecated.
src/Subscription/Subscriber · high confidence
Replace custom clock implementations with PSR-20 compatible interfaces
The custom clock implementations in src/Clock have been replaced with new classes (FrozenClock and SystemClock) that implement the PSR-20 ClockInterface. This change ensures compatibility with the PSR-20 standard, allowing for better interoperability with other libraries and frameworks that adhere to this interface. The FrozenClock class now supports updating the frozen time and simulating sleep, while the SystemClock class provides the current system time.
src/Clock · high confidence
Repository API refactored to use AggregateRootId and support snapshots
The repository interface and implementation have been significantly restructured. The \Repository\ class is now an interface (\Repository.php\) that accepts and returns \AggregateRootId\ objects instead of raw strings, and the concrete \DefaultRepository\ handles loading via snapshots when available. A new \DefaultRepositoryManager\ automates repository instantiation per aggregate class. Several specific exception classes have been introduced or renamed (e.g., \AggregateNotFound\, \AggregateDetached\, \PlayheadMismatch\, \SnapshotRebuildFailed\) to provide clearer error reporting, while older exceptions like \AggregateNotFoundException\ and \WrongAggregateException\ have been removed. The system now supports message decoration via a \MessageDecorator\ interface and chain, enabling features like stream splitting.
src/Repository · high confidence
Subscription engine introduces new status states and error handling capabilities
The subscription engine now supports a 'failed' status in addition to existing states like 'error', 'detached', and 'active', allowing subscriptions to transition to a final failed state rather than remaining in an error state. The system also includes a new 'NoErrorToRetry' exception to prevent retry attempts when no error is present, and enhances error context transformation by flattening arguments with safeguards against deep nesting and long strings to improve debugging information.
src/Subscription · high confidence
Subscription engine overhaul: catch-up, gap detection, and retry strategies
The subscription engine in src/Subscription/Engine has been significantly restructured to improve reliability and performance. A new CatchUpSubscriptionEngine allows users to process backlog events in batches, while the GapResolverStoreMessageLoader introduces gap detection with configurable retry windows to handle transient store issues. Error handling is now more robust with a configurable RetryStrategyRepository (supporting clock-based and no-retry strategies) and a new ThrowOnErrorSubscriptionEngine that fails fast on errors. Additionally, the engine now supports event filtering via EventFilteredStoreMessageLoader to reduce load, and the RunSubscriptionEngineRepository automatically triggers subscription runs after saving aggregates.
src/Subscription/Engine · high confidence
Test coverage
Added architecture tests for class finality and layer dependencies; Added benchmark event definitions for profile operations; Added benchmark projector implementations for performance testing; Added benchmark suite for event sourcing performance; Added benchmark test fixtures for profile aggregates and command handling; Added benchmark test for SendEmailProcessor; Added integration test for BankAccountProjector; Added integration test for ProfileProjector; Added integration test for email sending processor; Added integration tests for Doctrine DBAL event stores; Added integration tests for basic event sourcing scenarios; Added integration tests for child aggregates; Added integration tests for event domain models; Added integration tests for micro-aggregate event sourcing with shared apply contexts; Added integration tests for personal data encryption and deletion; Added integration tests for subscription subscriber lifecycle and error handling; Added integration tests for the BankAccount split stream feature; Added integration tests for the subscription engine; Added test event classes for BankAccountSplitStream integration; Added test fixture for message header decoration; Added test fixtures for Header attribute usage; Added test fixtures for profile command and query operations; Added test helper for generating incremental UUIDs; Added test infrastructure and utilities; Added unit tests for AttributeSubscriberMetadataFactory; Added unit tests for ClassFinder metadata functionality; Added unit tests for Criteria and CriteriaBuilder; Added unit tests for DBAL cleanup task handler and default cleaner; Added unit tests for DefaultHeadersSerializer; Added unit tests for FrozenClock and SystemClock; Added unit tests for IdNormalizer; Added unit tests for Message, Pipe, Reducer, and HeaderNotFound components; Added unit tests for Subscription Lookup; Added unit tests for Upcast and UpcasterChain components; Added unit tests for aggregate root metadata and registry; Added unit tests for event metadata and registry components; Added unit tests for event serialization and upcasting; Added unit tests for event sourcing console commands; Added unit tests for event-sourcing attribute classes; Added unit tests for event-sourcing store implementations; Added unit tests for message translator components; Added unit tests for repository manager, default repository, and message decorators; Added unit tests for schema management components; Added unit tests for snapshot adapters and store components; Added unit tests for subscriber metadata and accessor components; Added unit tests for subscription retry strategies; Added unit tests for subscription subscriber argument resolvers; Added unit tests for the QueryBus component; Added unit tests for the subscription engine components; Expanded test fixtures for event-sourcing validation.
Dependencies
Major dependency upgrade and tooling separation
The library has been upgraded to require PHP 8.2+ and Doctrine DBAL 4.x, dropping support for older PHP versions and DBAL 2.x. This change introduces new dependencies including \patchlevel/hydrator\, \patchlevel/worker\, and \doctrine/migrations\, while removing legacy packages like \doctrine/cache\. Additionally, CI tooling dependencies (specifically \roave/backward-compatibility-check\) have been moved to a separate \tools/composer.json\ to isolate them from the main library's runtime requirements.
(dependencies) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Score
- CAI 66 → 70 (+4.0)
- Rubric changed (rubric-2026.08.19 → rubric-2026.09.15) — scores are not directly comparable.
Lenses
- Code Health 88 → 92 (+4.7)
- Architecture 95 → 96 (+1.0)
- Maturity 64 → 67 (+3.8)
- Readiness 69 → 66 (-3.5)
- Security 60 → 68 (+8.4)
Resolved (80)
- Boundary-crossing change coupling: AggregateHeader.php ↔ Message.php (src/Aggregate/AggregateHeader.php)
- Boundary-crossing change coupling: DefaultRepository.php ↔ CriteriaBuilder.php (src/Repository/DefaultRepository.php)
- Boundary-crossing change coupling: Repository.php ↔ SnapshotStore.php (src/Repository/Repository.php)
- Boundary-crossing change coupling: ShowAggregateCommand.php ↔ DefaultRepository.php (src/Console/Command/ShowAggregateCommand.php)
- Change coupling clique: SchemaCreateCommand.php, SchemaDropCommand.php, SchemaUpdateCommand.php (src/Console/Command/SchemaCreateCommand.php)
- Change coupling: ArrayStream.php ↔ DoctrineDbalStoreStream.php (src/Store/ArrayStream.php)
- Change coupling: DatabaseCreateCommand.php ↔ DatabaseDropCommand.php (src/Console/Command/DatabaseCreateCommand.php)
- Change coupling: SubscriptionRemoveCommand.php ↔ SubscriptionTeardownCommand.php (src/Console/Command/SubscriptionRemoveCommand.php)
- Coverage not included — suite not readable by the collector
- Dependency hygiene not measured — dependency manifest found but not parsed for hygiene
- Duplicated block (10 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (11 lines × 2) (src/Cryptography/DoctrineCipherKeyStore.php)
- Duplicated block (11 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (11 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 2) (src/Store/DoctrineDbalStoreStream.php)
- Duplicated block (12 lines × 2) (src/Subscription/Engine/DefaultSubscriptionEngine.php)
- Duplicated block (13 lines × 2) (src/CommandBus/HandlerDescriptor.php)
- Duplicated block (14 lines × 2) (src/CommandBus/HandlerFinder.php)
- …and 60 more
New (221)
- Boundary-crossing change coupling: DefaultRepository.php ↔ DefaultSnapshotStore.php (src/Repository/DefaultRepository.php)
- Boundary-crossing change coupling: SubscriptionStatusCommand.php ↔ DoctrineSubscriptionStore.php (src/Console/Command/SubscriptionStatusCommand.php)
- ClassTooLong: DefaultSubscriptionEngine (src/Subscription/Engine/DefaultSubscriptionEngine.php)
- Documentation: no usage examples (docs/cli.md)
- Duplicate intent: Trait vs Interface/Behavior. AggregateRoot (likely a base class or trait) and AggregateRootBehaviour (likely a trait) expose the exact same method signature for the same domain operation. This suggests either a copy-paste error in the trait definition or a confusing layering where users must implement the same logic in multiple places.
- Duplicate types with different names. HandlerDescriptor and ListenerDescriptor have identical signatures and intent (wrapping a callable with a name). This creates unnecessary type duplication in the API.
- Duplicated block (11 lines × 2) (src/Metadata/AggregateRoot/Psr16AggregateRootRegistryFactory.php)
- Duplicated block (11 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (11 lines × 3) (src/Metadata/AggregateRoot/Psr16AggregateRootMetadataFactory.php)
- Duplicated block (11–13 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (12 lines × 3) (src/CommandBus/HandlerDescriptor.php)
- Duplicated block (13 lines × 2) (src/CommandBus/HandlerDescriptor.php)
- Duplicated block (13 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (15 lines × 2) (src/Store/DoctrineDbalStore.php)
- Duplicated block (15 lines × 2) (src/Store/DoctrineDbalStoreStream.php)
- Duplicated block (15 lines × 2) (src/Subscription/Engine/DefaultSubscriptionEngine.php)
- Duplicated block (17 lines × 2) (src/CommandBus/HandlerFinder.php)
- Duplicated block (17 lines × 2) (src/Cryptography/DoctrineCipherKeyStore.php)
- …and 201 more
Changes since last survey
- 12 commits — 12 feature/other, 0 fixes
By area
- (repo) — 6 commits
- (root) — 4 commits
- .github/workflows — 2 commits
Notable commits
- change: Improve docs deployement to minimise CI times. * on versioned branches only trigger deploy if a changed landed in the docs * on release always trigger deploy
- change: Merge pull request #880 from patchlevel/xdebug-as-coverage-provider
- change: Merge pull request #881 from patchlevel/add-missing-docs
- change: Merge pull request #883 from patchlevel/export-docs-directory
- change: Merge pull request #892 from patchlevel/improve-deploy
- change: Merge pull request #894 from patchlevel/renovate/php
- change: Merge pull request #895 from patchlevel/renovate/mariadb-13.x
- change: Update dependency league/commonmark to v2.9.0 [SECURITY]
- change: Update mariadb Docker tag to v13
- change: Update php Docker tag to v8.5
- change: Update postgres Docker tag to v18.6
- change: export docs directory
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
patchlevel/event-sourcing was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 22 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit 53ebcb290cbacdea393bef0ce664d7352e2f7985 — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-821afab8930d.