fix: resolve a nested relation morph check against its own model - #3306
Open
OzanKurt wants to merge 5 commits into
Open
fix: resolve a nested relation morph check against its own model#3306OzanKurt wants to merge 5 commits into
OzanKurt wants to merge 5 commits into
Conversation
Relations are defined in camel case while column names are usually written in snake case, so a column named child_table.name never matched the eager loaded childTable relation and fell through to a plain query search on a table that is not joined. The camel case form is now used when the literal relation name is not an eager loaded one, which makes both spellings work without changing relations that already resolve under their literal name. Closes yajra#2324
The eager loads of the root query are keyed by their full path, e.g. user.childTable, so a nested segment never matched on its own and the raw snake name reached whereHas(), which fataled with an undefined method on the related model. A nested relation is now resolved against the model that declares it, which also covers a nested relation that is not eager loaded, while the root level keeps resolving against the eager loads as before.
Camel casing the whole dotted path failed on a path mixing a literal snake case relation with a camel case one, e.g. post_user.nestedHeart, which fell through to a plain column and produced an unknown column error when ordering. Each segment is now matched on its own, preferring the eager load that matches literally, and relations are detected with Model::isRelation() so that ones registered via resolveRelationUsing() are found too.
isMorphRelation() always checked the model the data table was built from, so a plain nested relation sharing its name with a morph relation of the root model was sent to whereHasMorph() and fataled with an undefined getMorphType() on the actual relation. The check now uses the model of the query being compiled, which is the related model inside a where has callback.
method_exists() misses a relation registered via resolveRelationUsing(), so such a relation was never recognised as a morph one.
OzanKurt
force-pushed
the
fix/nested-morph-relation
branch
from
September 5, 2026 18:45
6b1aa40 to
3ef1842
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Follow up to the note on #3300. Depends on #3300, so it is stacked on that branch and its two commits show up here until #3300 is merged. The morph fix itself is the last commit.
Problem
isMorphRelation()always resolves the relation against the model the data table was built from, even when it is called for a nested relation inside awhereHas()callback. At that point the relation belongs to the related model, not to the root one.When the root model happens to declare a morph relation under the same name as a plain relation of the nested model, the nested one is treated as a morph and
whereHasMorph()is called on a relation that is not morphed:Repro with the test models:
User::user()is amorphTowhilePost::user()is abelongsTo, so searchingposts.user.nameonUser::with('posts.user')fatals.The opposite direction, a genuine nested morph not being detected, turns out to be harmless. Laravel's
has()already routes aMorphTotohasMorph($relation, '*'), sowhereHas()produces the same per type existence queries. There is a test for that case so it stays that way.Solution
The morph check now resolves against the model of the query being compiled: the related model inside a
whereHas()callback, the root model otherwise. This is the same context that #3300 introduced for resolving relation names, so both now agree on which model a nested segment belongs to.Changes
isMorphRelationOf()performs the check against a given model, andisMorphRelation()keeps its signature and delegates using the root model, so an override of it in an application keeps working.modelOf()returns the model of the query being compiled, defaulting to the root one.compileQuerySearch()resolves the context once and uses it for both the relation name and the morph check.tests/Integration/NestedMorphRelationTest.phpcovers the fatal case and a genuine nested morph relation. The first one fails without the fix.Notes
composer stanpasses and the existingMorphToRelationTestpasses unchanged.