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

[12.x] Added Automatic Relation Loading (Eager Loading) Feature #53655

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

litvinchuk
Copy link
Contributor

@litvinchuk litvinchuk commented Nov 24, 2024

Problem

In large projects that heavily rely on Eloquent models, managing relations often becomes a complex and error-prone task. This is especially true when a resource requires loading numerous nested relations, such as:

$model->load([
    'client.owner.details',
    'client.customPropertyValues',
    'clientContact.customPropertyValues',
    'status',
    'company.statuses',
    'posts.authors.articles.likes',
    'related.statuses'
]);

Challenges include:

  1. Unnecessary overhead when relations change: If the logic for loading relations changes and we forget to remove or update a load() or with() call, unnecessary relations may still be loaded, leading to performance inefficiencies.
  2. Tedious manual loading: Explicitly calling load() or with() for each relation makes the code verbose and harder to read.
  3. Maintenance overhead: As the number of relations grows, the related logic becomes increasingly difficult to maintain and prone to duplication.

To address these issues, this PR introduces a new feature that simplifies relation management by enabling automatic eager loading. This reduces boilerplate code and improves code readability.

New withRelationAutoload() Method

A new method, withRelationAutoload(), has been added to models and Eloquent collections. When called, it automatically loads relations whenever they are accessed, without the need for explicit load() or with() calls.

Example:

$orders = Order::all()->withRelationAutoload();

foreach ($orders as $order) {
    echo $order->client->owner->company->name;
}

// automatic calls:
// $orders->loadMissing('client');
// $orders->loadMissing('client.owner');
// $orders->loadMissing('client.owner.company');

Support for Morph Relations

The feature works seamlessly with polymorphic relations. It only loads the specific morph type that is accessed, ensuring efficient use of resources.

Doesn’t Break Manual Relation Loading

Users can still manually load relations using load() or with() before accessing the relation. If a relation is already loaded manually, it won’t be reloaded.

Global Automatic Loading

For cases where you want automatic loading enabled across all models, you can use the static method `

Model::globalAutoloadRelations();

This feature significantly simplifies working with relations and reduces the overhead of managing eager loading in Laravel projects, enabling developers to focus on application logic instead of data loading mechanics.

If you have suggestions for better method names, feel free to share them!

Copy link

Thanks for submitting a PR!

Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@litvinchuk litvinchuk marked this pull request as ready for review November 25, 2024 13:57
@litvinchuk litvinchuk changed the title Added Automatic Relation Loading (Eager Loading) Feature [12.x] Added Automatic Relation Loading (Eager Loading) Feature Nov 26, 2024
Copy link

@MykolaVoitovych MykolaVoitovych left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a very desired and powerful feature. It's going to save a lot of effort. I hope it will be approved soon. Thank you so much!

@stevebauman
Copy link
Contributor

stevebauman commented Nov 26, 2024

I'm not sure I understand this -- isn't this typical Eloquent lazy-loading behaviour? Won't the relations in your example be loaded via lazy-loading? Ex, this should work already:

$orders = Order::all();

foreach ($orders as $order) {
    echo $order->client->owner->company->name;
}

Or are you referring to preventing the possibility from N+1'ing due to the above lazy loading?

Please correct me if I'm misunderstanding 🙏

@litvinchuk
Copy link
Contributor Author

I'm not sure I understand this -- isn't this typical Eloquent lazy-loading behaviour? Won't the relations in your example be loaded via lazy-loading? Ex, this should work already:

$orders = Order::all();

foreach ($orders as $order) {
    echo $order->client->owner->company->name;
}

Or are you referring to preventing the possibility from N+1'ing due to the above lazy loading?

Please correct me if I'm misunderstanding 🙏

You’re absolutely right, and typically, methods like load, loadMissing, or with are used to address the N+1 problem in Eloquent. However, the challenge often arises in scenarios like views, JSON resources, or other parts of the code where a large number of relations are being accessed.

In such cases, it can become difficult to track and manually specify which relations should be eager-loaded, especially if those relations are deeply nested or dynamically used.

The withRelationAutoload() method simplifies this by automatically resolving the required relations the first time they are accessed, without requiring developers to explicitly define them in advance. This ensures that all necessary relations are eager-loaded efficiently in bulk, preventing N+1 problems while reducing the overhead of managing relation loading manually.

In your example:

$orders = Order::all();

foreach ($orders as $order) {
    echo $order->client->owner->company->name;
}

If there are 10 orders, here’s what happens:
In total, this results in 31 queries (1 for orders + 10 for client + 10 for owner + 10 for company).

With withRelationAutoload:
In total, this results in 4 queries (1 for orders + 1 for client + 1 for owner + 1 for company).

It's very simple example, we can just use with the same result

$orders->load('client.owner.company');

But if we change our code in the future, the relations will be loaded unnecessarily until we explicitly remove them from the load.

foreach ($orders as $order) {
    echo $order->client->name;
}

Let me know if this makes sense or if you’d like further clarification! 🙏

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.

3 participants