DDC-357: Lazy-loading in OneToOne-bidirectional associations not working for inverse side #444

Closed
opened 2026-01-22 12:38:26 +01:00 by admin · 22 comments
Owner

Originally created by @doctrinebot on GitHub (Feb 21, 2010).

Originally assigned to: @beberlei on GitHub.

Jira issue originally created by user m.walter:

I am referring to the following situation:
http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#one-to-one,-bidirectional

In this example: if I fetch an object of type "Customer" from my database, a second query is executed immediately that fetches the corresponding "Cart" even if I do not access the $cart property of "Customer". Annotating fetch="LAZY" to the $cart property does not make any difference. This is even worse in case of self-referencing objects, e.g. those having at most one parent object and at most one child object. Here, all associations are created by single database queries at once (e.g. fetching the child object, then the child of the child object and so forth).

By contrast, OneToMany associations are lazy-loaded from the inverse side (as expected).

Perhaps I should add, that I am using annotation mappings for my entities (no YAML, no XML).

Originally created by @doctrinebot on GitHub (Feb 21, 2010). Originally assigned to: @beberlei on GitHub. Jira issue originally created by user m.walter: I am referring to the following situation: http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#one-to-one,-bidirectional In this example: if I fetch an object of type "Customer" from my database, a second query is executed immediately that fetches the corresponding "Cart" even if I do not access the $cart property of "Customer". Annotating fetch="LAZY" to the $cart property does not make any difference. This is even worse in case of self-referencing objects, e.g. those having at most one parent object and at most one child object. Here, all associations are created by single database queries at once (e.g. fetching the child object, then the child of the child object and so forth). By contrast, OneToMany associations are lazy-loaded from the inverse side (as expected). Perhaps I should add, that I am using annotation mappings for my entities (no YAML, no XML).
admin added the Bug label 2026-01-22 12:38:26 +01:00
admin closed this issue 2026-01-22 12:38:27 +01:00
Author
Owner

@doctrinebot commented on GitHub (Feb 21, 2010):

@doctrinebot commented on GitHub (Feb 21, 2010): - is referenced by [DDC-3011: [GH-970] [DDC-357] Effective toOne joins](http://www.doctrine-project.org/jira/browse/DDC-3011)
Author
Owner

@doctrinebot commented on GitHub (Feb 21, 2010):

Comment created by romanb:

This is expected behavior. Inverse sides of one-to-one associations can not be lazy, technically. There is no foreign key on the inverse side, hence it is impossible to decide whether to proxy it or not. We must query for the associated object or join it. Note that this only affects inverse sides of single-valued associations, that is, really only the inverse side of bidirectional one-to-one associations.

In the future, you can use fetch="EAGER" to automatically load the associated objects in a join whenever the inverse side object is loaded. That is a planned enhancement.
So when fetch="EAGER" is used on a single-valued association, it is automatically fetch-joined, even when you just do ->find('Object', 1).

Right now, you can use an eager fetch join in DQL to avoid the extra query. Fetch-joins on single-valued associated are usually very cheap, compared to collections.

Note that you need a join anyway, because the foreign key is on the other side, thus it doesnt make much sense to join just for the sake of getting the foreign key. If we join we can as well grab the associated object completely.

The "fetch" mode is a "hint", that means, Doctrine tries to do that when possible. Its not always possible.

If you have a suggestion, feel free to speak up.

@doctrinebot commented on GitHub (Feb 21, 2010): Comment created by romanb: This is expected behavior. Inverse sides of one-to-one associations can not be lazy, technically. There is no foreign key on the inverse side, hence it is impossible to decide whether to proxy it or not. We must query for the associated object **or** join it. Note that this only affects inverse sides of single-valued associations, that is, really only the inverse side of bidirectional one-to-one associations. In the future, you can use fetch="EAGER" to automatically load the associated objects in a join whenever the inverse side object is loaded. That is a planned enhancement. So when fetch="EAGER" is used on a single-valued association, it is automatically fetch-joined, even when you just do ->find('Object', 1). Right now, you can use an eager fetch join in DQL to avoid the extra query. Fetch-joins on single-valued associated are usually very cheap, compared to collections. Note that you need a join anyway, because the foreign key is on the other side, thus it doesnt make much sense to join just for the sake of getting the foreign key. If we join we can as well grab the associated object completely. The "fetch" mode is a "hint", that means, Doctrine tries to do that when possible. Its not always possible. If you have a suggestion, feel free to speak up.
Author
Owner

@doctrinebot commented on GitHub (Feb 21, 2010):

Issue was closed with resolution "Won't Fix"

@doctrinebot commented on GitHub (Feb 21, 2010): Issue was closed with resolution "Won't Fix"
Author
Owner

@doctrinebot commented on GitHub (Feb 21, 2010):

Comment created by romanb:

If you have an example of a self-referential association that causes extreme ripple-loading of the whole hierarchy, can you please file a new jira issue for that?

Thanks!

@doctrinebot commented on GitHub (Feb 21, 2010): Comment created by romanb: If you have an example of a self-referential association that causes extreme ripple-loading of the whole hierarchy, can you please file a new jira issue for that? Thanks!
Author
Owner

@doctrinebot commented on GitHub (Feb 21, 2010):

Comment created by romanb:

Here some other ways to get around the extra queries:

  1. $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);

  2. $query->getArrayResult() / ->getScalarResult()

Just for the sake of completeness.

@doctrinebot commented on GitHub (Feb 21, 2010): Comment created by romanb: Here some other ways to get around the extra queries: 1) $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); 2) $query->getArrayResult() / ->getScalarResult() Just for the sake of completeness.
Author
Owner

@doctrinebot commented on GitHub (Mar 13, 2012):

Comment created by koc:

Why we cannot create proxy without FK?

@doctrinebot commented on GitHub (Mar 13, 2012): Comment created by koc: Why we cannot create proxy without FK?
Author
Owner

@doctrinebot commented on GitHub (Dec 10, 2012):

Comment created by naitsirch:

Hi. I have a problem with this solution.

I have an entity "Document" with an one-to-one association to an entity called "File". In the database table of the File entity I store the content of a file. So if I load for example 20 Documents I do not want the associated Files to be loaded, because this leads to memory problems.

I do not understand the explanation {quote}There is no foreign key on the inverse side, hence it is impossible to decide whether to proxy it or not.{quote}
In a one-to-many association there is no foreign key on the inverse side, too. Why not always load a proxy like in a one-to-many association?

Plus: There is no documentation about this, neither on http://docs.doctrine-project.org/en/latest/reference/association-mapping.html nor http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html

@doctrinebot commented on GitHub (Dec 10, 2012): Comment created by naitsirch: Hi. I have a problem with this solution. I have an entity "Document" with an one-to-one association to an entity called "File". In the database table of the File entity I store the content of a file. So if I load for example 20 Documents I do not want the associated Files to be loaded, because this leads to memory problems. I do not understand the explanation {quote}There is no foreign key on the inverse side, hence it is impossible to decide whether to proxy it or not.{quote} In a one-to-many association there is no foreign key on the inverse side, too. Why not always load a proxy like in a one-to-many association? Plus: There is no documentation about this, neither on http://docs.doctrine-project.org/en/latest/reference/association-mapping.html nor http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
Author
Owner

@doctrinebot commented on GitHub (Dec 10, 2012):

Comment created by romanb:

It is pretty simple, in a one-to-many association where the one-side is the inverse side, it holds a collection. The collection may be empty or not but there can always be a collection so it easy and correct to always put a proxy collection there.

If you have a single-valued side that is the inverse side, how can you decide whether to put a proxy object there or not? Without seeing the foreign key you have no way to distinguish between: There is no associated object (and thus putting a proxy object in place would by simply wrong) or there is one and of which type it is, if inheritance is involved (since putting a proxy of the wrong type in is also wrong).

This is as far as I recall, maybe Benjamin knows more on the current state.

@doctrinebot commented on GitHub (Dec 10, 2012): Comment created by romanb: It is pretty simple, in a one-to-many association where the one-side is the inverse side, it holds a collection. The collection may be empty or not but there can always be a collection so it easy and correct to always put a proxy collection there. If you have a single-valued side that is the inverse side, how can you decide whether to put a proxy object there or not? Without seeing the foreign key you have no way to distinguish between: There is no associated object (and thus putting a proxy object in place would by simply wrong) or there is one and of which type it is, if inheritance is involved (since putting a proxy of the wrong type in is also wrong). This is as far as I recall, maybe Benjamin knows more on the current state.
Author
Owner

@doctrinebot commented on GitHub (Dec 10, 2012):

Comment created by naitsirch:

Okay, I see the problem. But it would be nice if this behaviour could be documented.

Isn't it possible to always put a special proxy object there and if it get's accessed via lazy loading and Doctrine detect's that there is no associated entity, then the proxy will be replaced by a NULL value?

@doctrinebot commented on GitHub (Dec 10, 2012): Comment created by naitsirch: Okay, I see the problem. But it would be nice if this behaviour could be documented. Isn't it possible to always put a special proxy object there and if it get's accessed via lazy loading and Doctrine detect's that there is no associated entity, then the proxy will be replaced by a NULL value?
Author
Owner

@doctrinebot commented on GitHub (Feb 26, 2013):

Comment created by hrajchert:

Hi, I've been faced with this issue some times. I tent to solve this by doing two unidirectional one-to-one. Then I deal with the non existing relationship using a method called getRelation that I defined in a BaseModel.
I have created this method because Doctrine filled up with a Proxy when I fetch the entity without its relationship (then accesing the object would throw an Exception), and filled up with null when the object was eagerly fetched (left join) but no relationship was found.
I think we could add this getRelation in the entitymanager or a trait after php 5.4.

@doctrinebot commented on GitHub (Feb 26, 2013): Comment created by hrajchert: Hi, I've been faced with this issue some times. I tent to solve this by doing two unidirectional one-to-one. Then I deal with the non existing relationship using a method called getRelation that I defined in a BaseModel. I have created this method because Doctrine filled up with a Proxy when I fetch the entity without its relationship (then accesing the object would throw an Exception), and filled up with null when the object was eagerly fetched (left join) but no relationship was found. I think we could add this getRelation in the entitymanager or a trait after php 5.4.
Author
Owner

@doctrinebot commented on GitHub (Nov 10, 2013):

Comment created by stekycz:

Hi,
I think that idea of special proxy object is not so bad. However if you do not want to create special proxy object and build some logic around it then why do you not want to satisfy at least one of two (currently unsupported) cases?
I mean the case in which the object type is used in inheritance. This case is easier to solve in my opinion (similar to @OneToMany) and usually wanted. It is better to support at least one possibility then none.

@doctrinebot commented on GitHub (Nov 10, 2013): Comment created by stekycz: Hi, I think that idea of special proxy object is not so bad. However if you do not want to create special proxy object and build some logic around it then why do you not want to satisfy at least one of two (currently unsupported) cases? I mean the case in which the object type is used in inheritance. This case is easier to solve in my opinion (similar to @OneToMany) and usually wanted. It is better to support at least one possibility then none.
Author
Owner

@doctrinebot commented on GitHub (Mar 5, 2014):

Comment created by hosiplan:

I'm proposing a solution to this "Won't Fix" that I disagree with https://github.com/doctrine/doctrine2/pull/970

@doctrinebot commented on GitHub (Mar 5, 2014): Comment created by hosiplan: I'm proposing a solution to this "Won't Fix" that I disagree with https://github.com/doctrine/doctrine2/pull/970
Author
Owner

@doctrinebot commented on GitHub (Mar 23, 2014):

Comment created by @doctrinebot:

A related Github Pull-Request [GH-970] was closed:
https://github.com/doctrine/doctrine2/pull/970

@doctrinebot commented on GitHub (Mar 23, 2014): Comment created by @doctrinebot: A related Github Pull-Request [GH-970] was closed: https://github.com/doctrine/doctrine2/pull/970
Author
Owner

@doctrinebot commented on GitHub (Sep 11, 2014):

Comment created by sNICkerssss:

My solution:
Two entities with one-to-one reference Site and Url:


Site

/****

  • @var Url
    *
  • @ORM\OneToOne(targetEntity="Url", cascade={"persist"})
  • @ORM\JoinColumns({
  • @ORM\JoinColumn(name="url_id", referencedColumnName="url_id")
  • })
    */
    private $url;

/****

  • @return Url
    */
    public function getUrl()
    {
    return $this->url;
    }

Url

/****

  • @var \Doctrine\ORM\PersistentCollection
    *
  • @ORM\OneToMany(targetEntity="Site", mappedBy="url")
    */
    private $site;

/****

  • @return Site|null
    */
    public function getSite()
    {
    return ($this->site->first() !== false) ? $this->site->first() : null;
    }

Queries:

$url = $urlRepo->createQueryBuilder('u')
->where('u.url_id = :url_id')->setParameter('url_id', 19518)
->getQuery()
->getOneOrNullResult();

We have only 1 query to DB.

When call:
$url->getSite();

One more query appears. So lazy load work perfect!


If with join:
$url = $urlRepo->createQueryBuilder('u')
->select('u', 's')
->join('u.site', 's')
->where('u.url_id = :url_id')->setParameter('url_id', 19518)
->getQuery()
->getOneOrNullResult();

When call:
$url->getSite();

Only 1 query to DB.

@doctrinebot commented on GitHub (Sep 11, 2014): Comment created by sNICkerssss: My solution: Two entities with one-to-one reference Site and Url: --- Site /**** - @var Url * - @ORM\OneToOne(targetEntity="Url", cascade={"persist"}) - @ORM\JoinColumns({ - @ORM\JoinColumn(name="url_id", referencedColumnName="url_id") - }) */ private $url; /**** - @return Url */ public function getUrl() { return $this->url; } --- Url /**** - @var \Doctrine\ORM\PersistentCollection * - @ORM\OneToMany(targetEntity="Site", mappedBy="url") */ private $site; /**** - @return Site|null */ public function getSite() { return ($this->site->first() !== false) ? $this->site->first() : null; } --- Queries: $url = $urlRepo->createQueryBuilder('u') ->where('u.url_id = :url_id')->setParameter('url_id', 19518) ->getQuery() ->getOneOrNullResult(); We have only 1 query to DB. When call: $url->getSite(); One more query appears. So lazy load work perfect! --- If with join: $url = $urlRepo->createQueryBuilder('u') ->select('u', 's') ->join('u.site', 's') ->where('u.url_id = :url_id')->setParameter('url_id', 19518) ->getQuery() ->getOneOrNullResult(); When call: $url->getSite(); Only 1 query to DB.
Author
Owner

@doctrinebot commented on GitHub (Sep 11, 2014):

Comment created by hosiplan:

That is not a solution but a workaround which we are also using.

@doctrinebot commented on GitHub (Sep 11, 2014): Comment created by hosiplan: That is not a solution but a workaround which we are also using.
Author
Owner

@doctrinebot commented on GitHub (Sep 11, 2014):

Comment created by sNICkerssss:

I wrote it for people who has such problem and don't know what to do with not necessary subqueries.

@doctrinebot commented on GitHub (Sep 11, 2014): Comment created by sNICkerssss: I wrote it for people who has such problem and don't know what to do with not necessary subqueries.
Author
Owner

@doctrinebot commented on GitHub (Oct 29, 2015):

Comment created by David Kaštánek:

Elegant. I'm also going to use this workaround to reduce the number of unnecessary subqueries. Thanks for sharing!

@doctrinebot commented on GitHub (Oct 29, 2015): Comment created by David Kaštánek: Elegant. I'm also going to use this workaround to reduce the number of unnecessary subqueries. Thanks for sharing!
Author
Owner

@ABM-Dan commented on GitHub (Feb 29, 2016):

There are several questions on stackoverload dealing with this, and people are switching to faux ManyToOne relationships and using setters and getters to get the index 0 of the faux array.

Is this really something that we can't possibily implement? I am personally in a situation where I would benefit from such a feature.

@ABM-Dan commented on GitHub (Feb 29, 2016): There are several questions on stackoverload dealing with this, and people are switching to faux ManyToOne relationships and using setters and getters to get the index 0 of the faux array. Is this really something that we can't possibily implement? I am personally in a situation where I would benefit from such a feature.
Author
Owner

@DHager commented on GitHub (Feb 29, 2016):

Just looking at the dates, is this still an issue? It sounds like folks are looking for $foo->getBar() to return an association proxy, when the table containing Bar data is the one that has a column referring to a Foo's primary key.

If so, can we get a test-case? I'm not sure if #1241 could be relevant, moving forward.

@DHager commented on GitHub (Feb 29, 2016): Just looking at the dates, is this still an issue? It sounds like folks are looking for `$foo->getBar()` to return an association proxy, when the table containing `Bar` data is the one that has a column referring to a `Foo`'s primary key. If so, can we get a test-case? I'm not sure if #1241 could be relevant, moving forward.
Author
Owner

@ABM-Dan commented on GitHub (Mar 3, 2016):

I'm working on crafting a test-case, but I don't really know how to create one as I always use doctrine in Symfony, not sure how to manage it in an independent project. Tips?

@ABM-Dan commented on GitHub (Mar 3, 2016): I'm working on crafting a test-case, but I don't really know how to create one as I always use doctrine in Symfony, not sure how to manage it in an independent project. Tips?
Author
Owner

@Isinlor commented on GitHub (Mar 8, 2017):

It seems like it may be related to: https://github.com/doctrine/doctrine2/pull/970

Solution seems to be outlined in: https://github.com/doctrine/doctrine2/pull/970#issuecomment-38392615 (check also previous commit to the linked one)

If it can not be solved, then IMHO it should be clearly documented as a current limitation.

I will try to document it first and then see if it's feasible for me to learn doctrine internals enough to solve it. I don't guarantee anything tough.

@Isinlor commented on GitHub (Mar 8, 2017): It seems like it may be related to: https://github.com/doctrine/doctrine2/pull/970 Solution seems to be outlined in: https://github.com/doctrine/doctrine2/pull/970#issuecomment-38392615 (check also previous commit to the linked one) If it can not be solved, then IMHO it should be [clearly documented as a current limitation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional). I will try to document it first and then see if it's feasible for me to learn doctrine internals enough to solve it. I don't guarantee anything tough.
Author
Owner

@oojacoboo commented on GitHub (Apr 20, 2024):

So, would it not be possible, since PHP 7.4 now, to check if the property has been initialized? Can that not be sufficient as a "proxy" check for hydration? Obviously that wouldn't be available for nullable fields, but would at least get support for critical non-nullable relationships.

@oojacoboo commented on GitHub (Apr 20, 2024): So, would it not be possible, since PHP 7.4 now, to check if the property has been initialized? Can that not be sufficient as a "proxy" check for hydration? Obviously that wouldn't be available for nullable fields, but would at least get support for critical non-nullable relationships.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#444