Skip to content

fix(has-many): keep children when a nullable collection is reordered - #576

Merged
roxblnfk merged 2 commits into
2.xfrom
fix/hasmany-calc-deleted
Oct 2, 2026
Merged

roxblnfk merged 2 commits into
2.xfrom
fix/hasmany-calc-deleted

Conversation

@roxblnfk

@roxblnfk roxblnfk commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

🔍 What was changed

  • HasMany::calcDeleted() finds removed children with an spl_object_id() lookup instead of array_udiff(), so the result no longer depends on the order of items in the collection.
  • Integration case Case575 reproduces the bug for all drivers: reordering, prepending to, and replacing inside a nullable HasMany collection.

Note

The bug lost data, not just broke a contract: with a nullable HasMany whose child has a BelongsTo back to the parent, children that stayed in the collection had their foreign key set to NULL. Prepending a new item detached every existing child. Non-nullable relations were unaffected because the following attachStore() turned the wrong delete back into a store.

🤔 Why?

The old comparator returned only 0 or -1. array_udiff() sorts both arrays with it, so the diff reported live items as removed on every PHP version from 8.1 to 8.5.

📝 Checklist

…relation

Reordering, prepending to or replacing inside a nullable HasMany collection nulls the foreign key of children that stay in it, because the `calcDeleted()` comparator never returns a positive value and `array_udiff()` reports live items as removed (#575).

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
The `array_udiff()` comparator returned only 0 or -1, so the sort-based diff reported children that stayed in the collection as removed, and a nullable relation nulled their foreign keys. A lookup by `spl_object_id()` needs no ordering contract and runs in linear time. Fixes #575.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.62%. Comparing base (9272565) to head (d75abd4).

Additional details and impacted files
@@            Coverage Diff            @@
##                2.x     #576   +/-   ##
=========================================
  Coverage     91.62%   91.62%           
- Complexity     2028     2031    +3     
=========================================
  Files           132      132           
  Lines          5275     5277    +2     
=========================================
+ Hits           4833     4835    +2     
  Misses          442      442           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@roxblnfk
roxblnfk merged commit 4dd4ed4 into 2.x Oct 2, 2026
31 of 34 checks passed
@roxblnfk
roxblnfk deleted the fix/hasmany-calc-deleted branch October 2, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_udiff comparator in HasMany::calcDeleted() breaks sorting contract

1 participant