Conversation
alganet
commented
Apr 1, 2026
- Add EntityFactory::create(class-string, ...props) for constructing entities without calling the constructor (readonly-safe)
- Add EntityFactory::resolveClass(name) replacing createByName
- Add EntityFactory::withChanges(entity, ...changes) for immutable copies
- Add EntityFactory::isReadOnly(entity) for readonly class detection
- Add ReadOnlyViolation exception for initialized readonly property guard
- Remove EntityFactory::createByName, hydrate, and disableConstructor
- Extend persist() to consult identity map for untracked entities, enabling update-by-replacement for immutable entities
- Add Collection::persist(...$changes) with inline withChanges support
- Change persist() return type from bool to object (returns the entity)
- Replace resolveEntityName with Typed::resolveEntityClass using FQN
- Lift resolveEntityClass into Base hydrator, shared by Flat and Nested
- Cache resolveClass and detectRelationProperties results
- Use SplObjectStorage::offsetUnset instead of deprecated detach
- Normalize terminology: PK/FK -> identity/reference throughout
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #24 +/- ##
============================================
+ Coverage 97.67% 98.11% +0.43%
- Complexity 256 283 +27
============================================
Files 16 16
Lines 516 583 +67
============================================
+ Hits 504 572 +68
+ Misses 12 11 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for immutable/readonly entities by introducing constructor-less entity creation/copying in EntityFactory, updating hydrators to resolve concrete entity classes (including typed discriminators), and extending persistence to support update-by-replacement via the identity map.
Changes:
- Introduces
EntityFactory::resolveClass(),create(class-string, ...props),withChanges(), andisReadOnly(), plus aReadOnlyViolationguard when attempting to modify initialized readonly properties. - Updates
Flat/Nestedhydrators (and sharedBase) to resolve entity classes (includingTyped) and instantiate entities without calling constructors. - Changes persistence to return the entity object and enables update-by-replacement for immutable entities by consulting/replacing entries in the identity map; adds
Collection::persist(...$changes)convenience.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/EntityFactory.php | Adds class resolution caching, constructor-less creation, readonly detection/guard, and immutable copying via withChanges(). |
| src/ReadOnlyViolation.php | Adds a dedicated exception for readonly-property mutation attempts. |
| src/AbstractMapper.php | Changes persist() to return the entity and adds identity-map replacement logic for update-by-replacement. |
| src/Collections/Collection.php | Updates persist() to return the entity and adds inline ...$changes support via withChanges(). |
| src/Collections/Typed.php | Switches typed resolution to return a concrete class-string via EntityFactory::resolveClass(). |
| src/Collections/Filtered.php | Terminology-only comment update (identifier wording). |
| src/Hydrators/Base.php | Centralizes entity-class resolution (Typed vs non-Typed) and aligns naming to “identity”. |
| src/Hydrators/Flat.php | Instantiates entities via create(resolveClass(...)) and converts typed entities by creating/copying without constructors. |
| src/Hydrators/Nested.php | Instantiates entities via create(resolveEntityClass(...)) rather than name-based construction. |
| tests/EntityFactoryTest.php | Adds coverage for readonly behavior, class resolution, and withChanges() semantics. |
| tests/AbstractMapperTest.php | Adds coverage for identity-map replacement, readonly inserts/updates, and persist() returning the entity. |
| tests/Collections/CollectionTest.php | Adds coverage for Collection::persist(...$changes) returning copies and flushing updates for immutable entities/graphs. |
| tests/Collections/TypedTest.php | Updates typed resolution expectations to return FQCNs via resolveEntityClass(). |
| tests/Hydrators/FlatTest.php | Adjusts hydrator tests to new factory usage and collection stacking patterns. |
| tests/Hydrators/NestedTest.php | Adjusts nested hydrator tests to explicit stacking (and child wiring expectations). |
| tests/InMemoryMapper.php | Renames PK/FK variables to id/ref and updates helper naming accordingly. |
| tests/Stubs/ReadOnlyAuthor.php | Adds readonly stub entity for tests. |
| tests/Stubs/ReadOnlyPost.php | Adds readonly stub entity for tests. |
| tests/Stubs/ReadOnlyComment.php | Adds readonly stub entity for tests. |
| tests/Stubs/Immutable/Author.php | Adds immutable readonly stub entity for graph tests. |
| tests/Stubs/Immutable/Post.php | Adds immutable readonly stub entity for graph tests. |
| tests/Stubs/Immutable/Comment.php | Adds immutable readonly stub entity for graph tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add EntityFactory::create(class-string, ...props) for constructing entities without calling the constructor (readonly-safe) - Add EntityFactory::resolveClass(name) replacing createByName - Add EntityFactory::withChanges(entity, ...changes) for immutable copies - Add EntityFactory::isReadOnly(entity) for readonly class detection - Add ReadOnlyViolation exception for initialized readonly property guard - Remove EntityFactory::createByName, hydrate, and disableConstructor - Extend persist() to consult identity map for untracked entities, enabling update-by-replacement for immutable entities - Add Collection::persist(...$changes) with inline withChanges support - Change persist() return type from bool to object (returns the entity) - Replace resolveEntityName with Typed::resolveEntityClass using FQN - Lift resolveEntityClass into Base hydrator, shared by Flat and Nested - Cache resolveClass and detectRelationProperties results - Use SplObjectStorage::offsetUnset instead of deprecated detach - Normalize terminology: PK/FK -> identity/reference throughout