[13.x] Check the class before calling relationLoaded in loadMissingRelationshipChain - #60993
Merged
taylorotwell merged 1 commit intoAug 3, 2026
Conversation
… chain loadMissingRelationshipChain() calls relationLoaded() on every plucked relation value before checking that the value is of the expected class. A relation does not have to hold a model or a collection of models, so anything else there hits a method it does not have. A paginator is the common case, since it is neither null nor a Collection, so it is not skipped and not collapsed, and the recursive call then reaches it. That fails with 'Method Illuminate\Database\Eloquent\Collection::relationLoaded does not exist', because the paginator forwards the call to its underlying collection. Checking the class first short circuits before that call, which leaves the filter semantics unchanged: values of the expected class are still matched exactly, and values of any other type are now skipped instead of being called into.
bojmaliev
added a commit
to bojmaliev/laravel-framework
that referenced
this pull request
Aug 5, 2026
…ionship-aggregates Resolves a conflict in loadMissingRelationshipChain() against laravel#60993, which reordered the filter to check the model class before calling relationLoaded(). Adopts that ordering, and applies the same ordering to the two filters this branch adds, since a relation value that is not of the expected class reaches them the same way.
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.
loadMissingRelationshipChain()callsrelationLoaded()on a value before it checks what that value is:A relation doesn't have to hold a model or a collection of models. If it holds something else, that value isn't null and isn't a
BaseCollection, so it gets past the filter, doesn't get collapsed by the check below, and the recursive call ends up callingrelationLoaded()on it.I hit this with a paginator.
AbstractPaginatorforwards unknown calls to the collection underneath it, so it comes out as:Lighthouse sets a
LengthAwarePaginatoras the relation value when it resolves a paginated GraphQL relation:https://github.com/nuwave/lighthouse/blob/master/src/Execution/ModelsLoader/PaginatedModelsLoader.php#L191
so any app using that hits this once
Model::automaticallyEagerLoadRelationships()is turned on. It's reported there as nuwave/lighthouse#2679.Swapping the two conditions makes the class check short circuit first. It's still an exact class comparison rather than
instanceof, so anything that matched before still matches — the only change is that a value of some other type gets skipped instead of called into.For anyone using a package that does this, automatic eager loading currently can't be turned on at all: the request 500s instead of rendering. After this they can use the feature, and relations holding something other than a model are simply left alone rather than crashing the request.
Nothing existing changes. The filter still matches the same models it did before, and the only values affected are ones that would have thrown.
Two tests. One walks a normal chain and checks the relations still get loaded — it passes before and after, so it shows the reorder doesn't change which models are matched. The other puts a paginator in a relation and walks a two step chain over it — that one fails on 13.x with the exception above and passes with this change.
$this->filter(function ($model) use ($relation, $class) { return ! is_null($model) && - ! $model->relationLoaded($relation) && - $model::class === $class; + $model::class === $class && + ! $model->relationLoaded($relation); })->load($relation);