Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

michapietsch's avatar

Eager load relation, but remove the relations own eager loads and appends

Hey everybody,

I have legacy code with a Child model with quite some eager loads and appended attributes, and when I include it as a relation (via with()), I get n+1 problems. I couldn't get to "clean up" the children inside the with(), but I solved it like shown in the example. I suspect this could be simplified a lot!

Could somebody please give a hint?

Please note: It's not about eager loading the children with the parent. Instead the Child has its own eagerly loaded relations, and some appended attributes for which it reaches into its relations. There is my challenge: unset all the unnecessary stuff that comes with the children by default.

$parents = parent::query()
    // some conditions
    ->get();

$children = Child::whereIn('parent_id', $parents->modelKeys())
    ->setEagerLoads([])
    // some conditions
    ->get()
    ->makeHidden([
        'someAppendedAttribute',
    ]);

$parents->each(function ($parent) use ($children) {
    $parent->setRelation('children', $children->filter(function ($child) use ($parent) {
        return $child->parent_id === $parent->id;
    })->values());
});
0 likes
5 replies
rodrigo.pedra's avatar

Might be the language barrier as I am not a native English speaker so apologize me if I didn't get what you mean.

As you want to perform an operation after the children are loaded (makeHidden) I don't see a way to do it in a single step. But, in my opinion the following approach seems clearer:

$parents = Parent::query()
    ->with([
        'children' => function ($relation) {
            $relation->setEagerLoads([])
                // some children's Eloquent related conditions
                // don't use get() here, as this closure will be run when eager loading children
            ;
        },
    ])
    // some parent's Eloquent related conditions
    ->get();

$parents->each(function (Parent $parent) {
    $parent->children->each->makeHidden([
        'someAppendedAttribute',
    ]);
});

This seems clearer for me because you group the fetching (before DB retrieval) logic and the processing (after DB retrieval) into two blocks of code.

Also as the parent's childrens are eager loaded you don't need to manually associate a parent to its children.

1 like
michapietsch's avatar

@rodrigo.pedra Thanks!

So you would include $relation->setEagerLoads([]) only to show the goal?

At first I tried this, but it did not have an effect. It even made it worse, because now the appended attributes caused even more queries. So I know I would need to generally revoke the appending of the attributes. But as far as I understand, makeHidden() only works on a single model level, not on query level.

This is legacy code, and in new models I am way more careful to add stuff to $with or $appends.

rodrigo.pedra's avatar

I actually kept the setEagerLoads from the previous code while refactoring.

If the legacy model has a $with property to auto-eager load related models it will do more harm than good as, you already saw it, it will prevent eager load and force lazy load.

So if you find it is better to remove it, remove it :)

$parents = Parent::query()
    ->with([
        'children' => function ($relation) {
                // REMOVE this closure if no eloquent conditions needs to be added here
                // some children's Eloquent related conditions
                // don't use get() here, as this closure will be run when eager loading children
        },
    ])
    // some parent's Eloquent related conditions
    ->get();
michapietsch's avatar

I just read for the first time about without():

From the docs:

$books = App\Book::without('author')->get();

3 likes

Please or to participate in this conversation.