Skip to content

Add strict loading and preloading of associations - #12590

Open
ruudk wants to merge 1 commit into
doctrine:3.7.xfrom
ruudk:strict-loading
Open

Add strict loading and preloading of associations#12590
ruudk wants to merge 1 commit into
doctrine:3.7.xfrom
ruudk:strict-loading

Conversation

@ruudk

@ruudk ruudk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Lazy loading hides database access behind a normal property read, so N+1
queries are usually found in production and not while writing the code.
This adds a way to detect them, and a way to fix them.

Strict loading reports a lazy load as a violation. It is off by default.
Modes are Disabled, NPlusOneOnly and All. A handler decides what happens:
throw, or log the violation and let the load happen.

Preloading loads an association for many entities at once. It is for
entities that are already in memory, where a fetch join can not help:

    $em->preload($users, ['articles.comments']);
    $repository->findAll(['articles']);
    $query->preload(['articles'])->getResult();

It also fixes the batching that the eager fetch modes already had. A
nested hydration flushed the batch too early, so one query per owner was
issued instead of one query for all of them. Many to many never batched
at all, and indexed collections were excluded.

Both features are inspired by Ruby on Rails, which has strict loading and preloading.

See discussion #10931.

@ruudk
ruudk changed the base branch from 3.6.x to 3.7.x August 30, 2026 12:14
Lazy loading hides database access behind a normal property read, so N+1
queries are usually found in production and not while writing the code.
This adds a way to detect them, and a way to fix them.

Strict loading reports a lazy load as a violation. It is off by default.
Modes are Disabled, NPlusOneOnly and All. A handler decides what happens:
throw, or log the violation and let the load happen.

Preloading loads an association for many entities at once. It is for
entities that are already in memory, where a fetch join can not help:

    $em->preload($users, ['articles.comments']);
    $repository->findAll(['articles']);
    $query->preload(['articles'])->getResult();

preload() is not on EntityManagerInterface, because adding a method to an
interface breaks every implementation of it. The EntityManager, the
decorator, the repository and the query each use a Preloader instead, so
the method can move to the interface in 4.0.

It also fixes the batching that the eager fetch modes already had. A
nested hydration flushed the batch too early, so one query per owner was
issued instead of one query for all of them. Many to many never batched
at all, and indexed collections were excluded.

Both features are inspired by Ruby on Rails, which has
[strict loading](https://guides.rubyonrails.org/active_record_querying.html#strict-loading)
and [preloading](https://guides.rubyonrails.org/active_record_querying.html#preload).

See discussion doctrine#10931.
@beberlei

Copy link
Copy Markdown
Member

This is too much at once, why add the preloading API in the same step?

The three mode names are not clear enough.

What means "also fixes batching wager loads"?

@ruudk

ruudk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

I agree this is too much. But for me it served more of a POC to see how I would envision this from a user api perspective.

Maybe a first step could be to detect and log n+1 issues and write those to a logfile.

But if you would enable that logging mode, how would you be able to preload all those relations at once? You need some kind of way to say: for these entities, load all the X relations in a single query.

Otherwise you end up with logs that are not actionable.

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.

2 participants