Skip to content

Load @with relations onto every instance of a duplicated row - #2807

Open
liliangiraudo5 wants to merge 2 commits into
nuwave:masterfrom
liliangiraudo5:fix-with-duplicate-parents
Open

liliangiraudo5 wants to merge 2 commits into
nuwave:masterfrom
liliangiraudo5:fix-with-duplicate-parents

Conversation

@liliangiraudo5

Copy link
Copy Markdown
  • Added or updated tests
  • Documented user facing changes

Changes

RelationBatchLoader::load() keyed parents by ModelKey, so a second instance of the same row under one path replaced the first, and only the surviving instance got the relation.
RelationDirective returns the batch result by key and is unaffected.
@with and @withCount run the field resolver on their own $parent, which never got the relation.
With a belongsToMany holding two pivot rows to the same model, that means a lazy load per duplicate (LazyLoadingViolationException under Model::preventLazyLoading()), or a missing *_count attribute.

 RelationBatchLoader::load($model)
-  parents[ModelKey($model)] = $model
+  parents[spl_object_id($model)] = $model

 PaginatedModelsLoader::load($parents)
-  one UNION subquery per parent
+  one UNION subquery per unique ModelKey
+  initRelation on every parent before match()

The same instance reached twice (e.g. a shared belongsTo) is still loaded once.

Breaking changes

None. No @api surface changes; RelationBatchLoader::$parents is now keyed by object id.

Changelog

Fixed

  • Load @with and @withCount relations onto every instance of a model that is hydrated more than once under the same path, e.g. through duplicate pivot rows

RelationBatchLoader kept one parent per model key, so when the same row
was hydrated into two instances under one path (e.g. a belongsToMany
with two pivot rows to the same model), only the last instance got the
relation. @with and @withCount then ran the field resolver on an
instance without it: a lazy load per duplicate, or a missing *_count
attribute.

The paginated loader dedupes its UNION subqueries by model key, so it
still runs one subquery per row.
@spawnia
spawnia requested a balanced review from Copilot October 25, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@spawnia

spawnia commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

🤖 @liliangiraudo5 #2803 fixes the same bug differently: @with and @withCount store the loaded result on their own parent, and the batch loader stays as it is.
Would that solve your problem too?

I lean towards your approach, since it fixes the batch loader for every caller.
The branch conflicts with master, so it needs an update before it can merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused loader changes preserve result lookup behavior, and targeted regression tests cover the affected cases with no blocking issues identified.

No open issues 🎉

Review effort: Balanced

# Conflicts:
#	src/Execution/ModelsLoader/PaginatedModelsLoader.php
@liliangiraudo5

Copy link
Copy Markdown
Author

Yes, #2803 would fix our case too, but I agree fixing the batch loader is better. Thanks for merging master in, nothing else to change on my side.

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.

3 participants