Replace constants with methods for identity and model version in aggregates.#2
Replace constants with methods for identity and model version in aggregates.#2gustavofreze merged 1 commit intomainfrom
Conversation
…on in aggregates.
There was a problem hiding this comment.
Pull request overview
This PR replaces reflective class-constant contracts in aggregates/entities with explicit protected methods, introduces named factories for core value objects, and adds infrastructure for upcaster chaining and event (de)serialization.
Changes:
- Replaced
IDENTITY/MODEL_VERSIONconstants withidentityName()/modelVersion()methods across entity/aggregate behaviors and updated tests/docs accordingly. - Made
Revision,SequenceNumber, andEventTypeconstructors private and added/expanded semantic factories (initial(),first(),of(),fromString()). - Added
Upcasterscollection withchain()plusIntermediateEventobject-mapping support (viatiny-blocks/mapper) and new upcast chain tests.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Upcast/UpcastersTest.php | Adds coverage for chaining multiple upcasters and pass-through behavior. |
| tests/Upcast/SingleUpcasterBehaviorTest.php | Updates revision construction to use factories (Revision::initial(), Revision::of()). |
| tests/Upcast/IntermediateEventTest.php | Extends coverage to fromIterable(), toArray(), and toJson() mapping/serialization. |
| tests/Snapshot/SnapshotTest.php | Updates SequenceNumber construction to SequenceNumber::first(). |
| tests/Models/ProductV2Upcaster.php | Adds a V2 upcaster fixture for chaining tests. |
| tests/Models/OrderWithoutIdentityConstant.php | Removes fixture now obsolete after dropping IDENTITY constant enforcement. |
| tests/Models/OrderWithMissingIdentityProperty.php | Migrates fixture to identityName() and new push() API + revision factories. |
| tests/Models/Order.php | Migrates fixture to identityName() and new push() API + revision factories. |
| tests/Models/CartWithoutIdentityConstant.php | Removes fixture now obsolete after dropping IDENTITY constant enforcement. |
| tests/Models/Cart.php | Migrates fixture to identityName()/modelVersion() methods + revision factory usage. |
| tests/Event/SequenceNumberTest.php | Adds factory/visibility assertions and migrates away from public constructor usage. |
| tests/Event/RevisionTest.php | Adds factory/visibility assertions and migrates away from public constructor usage. |
| tests/Event/EventTypeTest.php | Adds constructor visibility assertion and migrates invalid-value tests to fromString(). |
| tests/Event/EventRecordsTest.php | Updates record construction to use Revision::initial() and SequenceNumber::first(). |
| tests/Event/EventRecordTest.php | Updates record construction to use Revision::initial() and SequenceNumber::first(). |
| tests/Entity/EntityBehaviorTest.php | Removes constant-missing test and aligns expectations with identityName() contract. |
| tests/Aggregate/EventSourcingRootBehaviorTest.php | Removes constant-missing test, reflecting new identityName() approach. |
| src/Upcast/Upcasters.php | Introduces Upcasters collection with chain() to apply upcasters sequentially. |
| src/Upcast/Upcaster.php | Documentation update pointing users to Upcasters::chain(). |
| src/Upcast/SingleUpcasterBehavior.php | Updates revision bumping to Revision::of(...). |
| src/Upcast/IntermediateEvent.php | Implements mapper support (ObjectMapper + ObjectMappability) for reconstitution/serialization. |
| src/Internal/Exceptions/MissingIdentityConstant.php | Removes obsolete exception after moving away from IDENTITY constant. |
| src/Event/SequenceNumber.php | Makes constructor private and adds of() factory. |
| src/Event/Revision.php | Makes constructor private and adds initial()/of() factories. |
| src/Event/EventType.php | Makes constructor private (enforcing use of factories). |
| src/Entity/EntityBehavior.php | Replaces constant lookup with identityName() method contract. |
| src/Entity/Entity.php | Updates public API documentation to reflect identityName() method contract. |
| src/Aggregate/EventualAggregateRootBehavior.php | Renames pushEvent() to push() and keeps event recording behavior. |
| src/Aggregate/EventSourcingRootBehavior.php | Updates blank() identity assignment to use identityName(). |
| src/Aggregate/EventSourcingRoot.php | Updates PHPDoc to reflect identityName()/MissingIdentityProperty semantics. |
| src/Aggregate/AggregateRootBehavior.php | Switches model version resolution to modelVersion() and reorders helpers. |
| src/Aggregate/AggregateRoot.php | Updates API documentation to reflect modelVersion() method contract. |
| phpstan.neon.dist | Adds suppression for Upcasters::chain() return-type analysis issue. |
| composer.json | Adds tiny-blocks/mapper dependency. |
| README.md | Updates documentation/examples for identityName()/modelVersion(), push(), upcaster chaining, and mapping. |
| - message: '#Upcasters::chain\(\) should return#' | ||
| path: src/Upcast/Upcasters.php |
There was a problem hiding this comment.
phpstan.neon.dist adds an ignore for the Upcasters::chain() return-type error. This reduces static-analysis signal for a real typing issue; it would be better to adjust Upcasters::chain() so PHPStan can infer/guarantee the return type and then drop this suppression.
| - message: '#Upcasters::chain\(\) should return#' | |
| path: src/Upcast/Upcasters.php |
|
|
||
| $aggregate = new ReflectionClass(objectOrClass: static::class)->newInstanceWithoutConstructor(); | ||
| new ReflectionProperty($aggregate, static::IDENTITY)->setValue(objectOrValue: $aggregate, value: $identity); | ||
| new ReflectionProperty($aggregate, $aggregate->identityName()) |
There was a problem hiding this comment.
EventSourcingRootBehavior::blank() uses ReflectionProperty with $aggregate->identityName() directly. If identityName() returns a non-existent property, this will throw a ReflectionException instead of the library’s MissingIdentityProperty (and contradicts the EventSourcingRoot PHPDoc that now advertises MissingIdentityProperty). Consider resolving the property name via $aggregate->getIdentityName() (or explicitly checking property_exists and throwing MissingIdentityProperty) before constructing ReflectionProperty.
| new ReflectionProperty($aggregate, $aggregate->identityName()) | |
| $identityProperty = $aggregate->identityName(); | |
| if (!property_exists($aggregate, $identityProperty)) { | |
| throw new MissingIdentityProperty($identityProperty); | |
| } | |
| new ReflectionProperty($aggregate, $identityProperty) |
| $upcast = static function (IntermediateEvent $carried, Upcaster $upcaster): IntermediateEvent { | ||
| return $upcaster->upcast(event: $carried); | ||
| }; | ||
|
|
||
| return $this->reduce(accumulator: $upcast, initial: $event); |
There was a problem hiding this comment.
Upcasters::chain() currently relies on Collection::reduce(), but PHPStan cannot prove the reduced value is an IntermediateEvent (hence the new ignore rule). Instead of suppressing static analysis, consider rewriting chain() with an explicit fold over the collection (or adding a runtime type check before returning) so the declared return type is provably satisfied and the phpstan.neon ignore can be removed.
| $upcast = static function (IntermediateEvent $carried, Upcaster $upcaster): IntermediateEvent { | |
| return $upcaster->upcast(event: $carried); | |
| }; | |
| return $this->reduce(accumulator: $upcast, initial: $event); | |
| $carried = $event; | |
| foreach ($this as $upcaster) { | |
| $carried = $upcaster->upcast(event: $carried); | |
| } | |
| return $carried; |
No description provided.