Auditing a dirty, *uninitialized* to-many collection initializes it mid-flush, corrupting subsequent collection reads in the same request#102
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 10 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
I am going to adjust the implementation before merging. The main reason is that, for an uninitialized The safer approach is to reuse the already resolved old IDs as the base, then apply the collection insert/delete diffs:
I will also keep the change scoped to |
cc8b34d to
4749e34
Compare
|
Thank You. My earlier comment was about keeping #102 scoped and avoiding the snapshot/diff implementation for uninitialized PersistentCollection, because getSnapshot() is not always a reliable base. After rechecking the deferred audit materialization path you pointed out, I agree that this is a separate real issue. I wanted to handle this as a follow-up bug for the deferred materialization path with the correct implementation, rather than mixing it into the resolver fix. The follow-up fix will use Doctrine criteria loading after flush, so we can read the flushed collection IDs without hydrating the original PersistentCollection. I’ll make sure to credit you for identifying this path. Cheers! |
…tion Follow-up to #102. Suggested-by: Nikola Jovanovic Co-authored-by: Rahul Chavan
Problem
When an
#[Auditable]entity owns a lazy (EXTRA_LAZY) to-many association that is mutated while still uninitialized, the audit pass iterates the livePersistentCollectionto compute the recorded ids. Iterating an uninitialized collection forces Doctrine'sinitialize()to run aSELECTagainst the pre-commit database state duringflush(). The collection is hydrated from stale data and that stale snapshot sticks in theUnitOfWork, so later reads in the same request (->count(), iteration, lazy navigation) return incorrect pre-write values. It is silent in-memory corruption that only appears with auditing enabled. See #101 for the full write-up and reproduction.Fix
The audit pass must never initialize a collection as a side effect. This turned out to affect two code paths, not one:
CollectionChangeResolver::resolveCurrentCollectionIds()— the path reported in Auditing a dirty, *uninitialized* to-many collection initializes it mid-flush, corrupting subsequent collection reads in the same request #101. An already initializedPersistentCollectionis iterated as before; an uninitialized one has its current ids computed from the snapshot plus the pending insert/delete diffs, so it is never forced to load.PendingAuditPlanMaterializer::materialize()— the deferred-collection post-flush path. It also iterated the live collection (extractFromIterable($currentValue)) to materialize collection ids, re-introducing the exact same initialization side effect for entities that route through a deferred audit plan. It now uses the same snapshot/diff resolution when the value is an uninitializedPersistentCollection.The snapshot/diff logic is shared through a new
CollectionIdExtractor::extractIdsWithoutInitializing()helper so both call sites stay consistent. Insert/delete diffs are still reflected in the recorded ids, so collection auditing behaviour is unchanged.Tests
CollectionChangeResolverTest): drivesbuildCollectionTransition()with a real uninitializedPersistentCollectionand asserts the ids come from snapshot + diffs and that the collection is left uninitialized. Fails onmain, passes with the fix.CollectionInitializationSafetyTestwithLazyParent/LazyChild): an end-to-end regression on anEXTRA_LAZYinverse one-to-many. Children are added across several flushes and the test assertscount()keeps issuing a freshSELECT COUNT(*)and reports the true total instead of a value frozen mid-flush. Exercises the deferred-plan path and is green only with the fix.Verification
composer test): 918 tests.composer stan(PHPStan) andcomposer cs(php-cs-fixer) report no issues.