Skip to content

Comments

[12.x] Retain associative keys on eager loaded relations#58506

Merged
taylorotwell merged 3 commits intolaravel:12.xfrom
Jade-GG:feature/retain-associative-eager-relations
Jan 30, 2026
Merged

[12.x] Retain associative keys on eager loaded relations#58506
taylorotwell merged 3 commits intolaravel:12.xfrom
Jade-GG:feature/retain-associative-eager-relations

Conversation

@Jade-GG
Copy link
Contributor

@Jade-GG Jade-GG commented Jan 26, 2026

Fixes #57754.

TL:DR; this specifically arose from a very niche use case in one of our projects, where you may try to use the (currently undocumented) afterQuery function as part of an eager loaded relation, in which that afterQuery causes your collection to get become an associative collection. For example:

public function posts(): HasMany
{
    return $this
        ->hasMany(Post::class)
        ->afterQuery(fn($posts) => $posts->keyBy('id'));
}

If you use lazy loading, e.g., User::first()->posts, this already worked as I would have expected. However, when using eager loading, e.g., User::with('posts')->first()->posts the resulting array would become non-associative due to how the dictionary is being built. This PR changes that behaviour specifically for the case that the resulting array out of a relation is associative.


This (as far as I can tell) will not have any bad side effects as I've made sure this will only occur on non-associative arrays and only specifically in the dictionaries of relations. Non-associative arrays there won't normally occur unless you specifically want them to be there.

Of course I don't have the most in-depth knowledge about the inner workings of the core framework, so I could be wrong, however I wasn't able to find any counterexamples.

@dxnter
Copy link
Contributor

dxnter commented Jan 26, 2026

I understand why you modified Collection::mapToDictionary() to let HasOneOrMany::buildDictionary() just work, but I would be concerned about changing Collection::mapToDictionary() because:

  1. The docblock contract is violated. It explicitly states array<int, TMapToDictionaryValue> for inner arrays
  2. It's a general purpose collection method. Code outside Eloquent may rely on sequential keys
    • mapToGroups also calls mapToDictionary internally, so the changes cascade to that method too

Instead of modifying mapToDictionary, I think HasOneOrMany::buildDictionary() could be rewritten to match the pattern being used for BelongsToMany, HasOneOrManyThrough, and MorphTo. This would keep the fix contained to Eloquent without risking side effects in unrelated Collection usage.

One other minor note, since keys can be either int, or the original associate key type, array<array<array-key, TRelatedModel>> would be more accurate across all the buildDictionary methods.

@Jade-GG
Copy link
Contributor Author

Jade-GG commented Jan 27, 2026

Just to make sure the functionality stayed the same, I rewrote the function by copy-pasting the code from mapToDictionary and simplifying out anything we didn't need here. I've also changed the docblocks as suggested.

@taylorotwell taylorotwell merged commit dcbfc2f into laravel:12.x Jan 30, 2026
70 checks passed
@taylorotwell
Copy link
Member

Thanks 👍

@Jade-GG Jade-GG deleted the feature/retain-associative-eager-relations branch January 30, 2026 14:03
@Jamerze
Copy link

Jamerze commented Feb 13, 2026

After updating to Laravel 12.50.0 Model::with(['relation'])->get()->toArray() prefixes relation items with an index, before it didn't. Could this be related to this change? What is the desired outcome?

@Jade-GG
Copy link
Contributor Author

Jade-GG commented Feb 13, 2026

After updating to Laravel 12.50.0 Model::with(['relation'])->get()->toArray() prefixes relation items with an index, before it didn't. Could this be related to this change? What is the desired outcome?

I'm not experiencing the same (just checked to make sure). In principle, this code should only affect relations that already returned associative arrays when lazily loaded.

What kind of relation are you having this issue with?

@Jamerze
Copy link

Jamerze commented Feb 13, 2026

We are using the OctoberCMS AttachMany relation (https://github.com/octobercms/library/blob/4.x/src/Database/Relations/AttachMany.php) which is based on the MorphMany relation. The other relations are still working as before, indeed.

@Jade-GG
Copy link
Contributor Author

Jade-GG commented Feb 13, 2026

We are using the OctoberCMS AttachMany relation (https://github.com/octobercms/library/blob/4.x/src/Database/Relations/AttachMany.php) which is based on the MorphMany relation. The other relations are still working as before, indeed.

Cool, I will have a look later today.

@Jade-GG
Copy link
Contributor Author

Jade-GG commented Feb 17, 2026

@Jamerze Took a bit longer to find some time. But, unfortunately I've been unable to replicate your issue in a simple testing environment.

I don't get any difference (comparing v12.50.0 with a few previous versions) in the output of ->get()->toArray() on a simple MorphMany relation. I haven't set up OctoberCMS to try, given that I don't really know how it works and what I need for that 😅

Are you able to give me a working example where it goes wrong? If I can reproduce it I'm happy to debug and look for a fix.

@Jamerze
Copy link

Jamerze commented Feb 18, 2026

@Jade-GG Thanks for testing this. I tested it as well and it looks more like an OctoberCMS issue rather than something caused by this PR. I’ll open an issue there to check whether the functionality broke due to the Laravel update or something else.

daftspunk added a commit to octobercms/library that referenced this pull request Feb 19, 2026
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.

4 participants