Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eloquent: Nested Lazy Eager Loading will erase relations from Nested Eager Loading #24121

Closed
SeparateReality opened this issue May 5, 2018 · 9 comments

Comments

@SeparateReality
Copy link

  • Laravel Version: slim/slim 3.10.0
  • Eloquent: illuminate/database v5.6.20
  • PHP Version: 7.2.4
  • Database Driver & Version: MariaDB 10.3.6

Description:

When first using Nested Eager Loading, like
(1) $categories = Category::with(['products.adjunctProducts'])->get();
and put a Nested Lazy Eager Loading on top of it, like
(2) $categories->load(['products.subscriptionModel']);
then

after (1) you will get a Collection with 'products.adjunctProducts' relation, as expected.

But after (2) you will get a Collection with 'products.subscriptionModel' relation (again as expected),
but you will not find the relation 'products.adjunctProducts' anymore.

Remarks:

  • This does not happen with non-nested Eager Loads (e.g. $categories = Category::with('products')->get();
  • One could ask 'Why not just put the nested load directly in the first with()'.
    The described case here is simplified as much as possible. Our Eager Loaded collection normally can be changed by different modules using an event. So if a modules needs further relations it would just use load()
  • Our temporary solution is to not use get() (until after the event) but work with the Builder-Object and concatenate several with()s. Not sure if that's the best solution!?

Steps To Reproduce:

Take a model with different relations on a first relation and reproduce with the description above.

@staudenmeir
Copy link
Contributor

staudenmeir commented May 5, 2018

That's the intended behavior. Use this:

$categories->products->load(['subscriptionModel']);

@SeparateReality
Copy link
Author

Thank you for your quick reply. Unfortunately its plain wrong.
As described $categories is a collection. So your suggestion will end up in a PHP fatal error. It would be great if you could have a more thorough look at it.

It still is a bug

Why? Nested Eager Loading is well documented. Lazy Loading, as well.
How would someone expect that the previously nested load disappears when doing a lazy load afterwards?

I suggest one of those ways forward

Either fix the bug

or document it here: https://laravel.com/docs/5.6/eloquent-relationships#lazy-eager-loading

some more explanation:

  1. To mitigate the bug one could use the solution suggested above in a foreach() loop to add the load to each model (maybe that was the intention?). We tried that first. Compared to the concatenated with() in the Builder-object it unfortunately took 7 times longer (over 3 sec in our case).
    In our case this would look like the following. Nothing you want to do to a database :)
foreach ($categories as $category) {
     foreach ($category->childCategories as $child) {
          foreach ($child->products as $product) {
               $product->load('subscriptionModel');
          }
     }
}
  1. We also tried to use Collection::merge(). Since there is no deep copy for collections in Eloquent the easiest way to go was to recreate the original collection a second time, but this time use nested eager loading for the additional relations. Then do the merge between the two.
    Unfortunately it ends up with the same buggy result as above: merge() will remove the nested relations of one of the two collections. Maybe that's a hint to discover the underlying bug.
    This is the simplified but working code:
$categories1 = Category::with(['products.adjunctProducts'])->get();
$categories2 = Category::with(['products.subscriptionModel'])->get();

$merged = $categories1->merge($categories2);
$merged->all();

Resulting bug: You will not find any 'adjunctProducts'-Relations in $merged

I would really appreciate if someone could take a serious look at this. I am happy to help and provide more information as needed.

@staudenmeir
Copy link
Contributor

Sorry, I missed that. Use this:

$categories->pluck('products')->collapse()->load(['subscriptionModel']);

The purpose of load() is to get the relationship results – even if a model already has results. The idea behind overwriting existing results is that you might want to get the latest data.

I can see why you would expect a different behavior. For a single model and non-nested relationships you can use the loadMissing() method.

Your situation would require loadMissing() on the collection and support for nested relationships. The latter is not trivial to implement, since you would have to check for existing results in every single model (#23027).

You're welcome to propose or implement improvements.

@SeparateReality
Copy link
Author

Thank you very much for elaborating on that!
The suggested line of code unfortunately ends up with an "Uncaught BadMethodCallException: Method Illuminate\Support\Collection::load does not exist". But no worries, maybe I missed something specific to our implementation. I just gave it a quick try.
Anyway, I think that was the push in the right direction which I was hoping for!

@staudenmeir
Copy link
Contributor

staudenmeir commented May 7, 2018

Again, my bad:

use Illuminate\Database\Eloquent\Collection;

(new Collection($categories->pluck('products')->collapse()))->load(['subscriptionModel']);

A shorter way to achieve this would definitely be nice...

@SeparateReality
Copy link
Author

Perfect!
It needed one more pair of brackets around the new(...). So the complete line (if someone else stumbles over this conversation) looks like this:
$newColl = (new Collection($categories->pluck('products')->collapse()))->load(['subscriptionModel']);
You saved us some hours of guessing! Thanks a lot!!

@staudenmeir
Copy link
Contributor

I updated my post. One would think that three attempts should be enough to write a working one-liner...

@SeparateReality
Copy link
Author

One has to admit, its certainly not the most obvious one-liner... Thanks! 🥇

@staudenmeir
Copy link
Contributor

I proposed Collection::loadMissing(): #24166

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

No branches or pull requests

3 participants