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

Loading inverse side of a one-to-one fails if target entity has an association id #11108

Closed
mcurland opened this issue Dec 6, 2023 · 2 comments · Fixed by #11109
Closed

Loading inverse side of a one-to-one fails if target entity has an association id #11108

mcurland opened this issue Dec 6, 2023 · 2 comments · Fixed by #11109

Comments

@mcurland
Copy link
Contributor

mcurland commented Dec 6, 2023

Bug Report

An inverse one-to-one is resolved with a separate query in EntityPersister->loadOneToOneEntity, which is called by UnitOfWork immediately after entity construction. If this is not the owning side of the association, a freshly constructed entity is provided as the source with the assumption that sufficient data is available in the entity to resolve the association.

The problem is that this is simply not true for an entity that uses an association as its identifier. In this case, the identifying association has not been initialized, so the entity has exactly zero useful information in it. This throws in loadOneToOneEntity indicating that the join column is not mapped.

The result is that the owning side of the relationship needs to be pulled into the UoW before the inverse side. Obviously, I should be able to query either object without failure

Q A
BC Break no (assuming BC means Backwards Compatibility, which is NO (Not Obvious))
Version 2.17.1 (ongoing)

Summary

Entity identified with an association cannot be loaded if it is the inverse side of a non-identifying one-to-one relationship.

Current behavior

Exception is thrown indicating that the join column cannot be identified.

How to reproduce

Snippets pulled from associated pull request (which does not fail because it includes a fix). Any attempt to directly retrieve an InverseSide (findOneBy, getResult, etc.) that is not already cached will throw.

/**
 * @Entity()
 * @Table(name="one_to_one_inverse_side_assoc_id_load_inverse")
 */
class InverseSide
{
    /**
     * Associative id (owning identifier)
     *
     * @var InverseSideIdTarget
     * @Id()
     * @OneToOne(targetEntity=InverseSideIdTarget::class, inversedBy="inverseSide")
     * @JoinColumn(nullable=false, name="associativeId")
     */
    public $associativeId;

    /**
     * @var OwningSide
     * @OneToOne(targetEntity=OwningSide::class, mappedBy="inverse")
     */
    public $owning;
}

/**
 * @Entity()
 * @Table(name="one_to_one_inverse_side_assoc_id_load_owning")
 */
class OwningSide
{
    /**
     * @var string
     * @Id()
     * @Column(type="string", length=255)
     * @GeneratedValue(strategy="NONE")
     */
    public $id;

    /**
     * Owning side
     *
     * @var InverseSide
     * @OneToOne(targetEntity=InverseSide::class, inversedBy="owning")
     * @JoinColumn(name="inverse", referencedColumnName="associativeId")
     */
    public $inverse;
}

/**
 * @Entity()
 * @Table(name="one_to_one_inverse_side_assoc_id_load_inverse_id_target")
 */
class InverseSideIdTarget
{
    /**
     * @var string
     * @Id()
     * @Column(type="string", length=255)
     * @GeneratedValue(strategy="NONE")
     */
    public $id;

    /**
     * @var InverseSide
     * @OneToOne(targetEntity=InverseSide::class, mappedBy="associativeId")
     */
    public $inverseSide;
}

Expected behavior

The expected behavior is that I can directly retrieve OwningSide and InverseSide in a symmetric fashion.

Approach to fix

The exception indicates that there is no field/column mapping for the associativeId field because it is used in association join column, so the first attempt to this added code to resolve the column. This was ultimately fruitless because even though the field was resolved, its contents are null because the owning relationship for the identifier has not been processed at this point.

The sourceEntity instance passed to loadOneToOneEntity is created immediately before this call, which moves data from the provided column-keyed array to the entity columns, with associations populated afterward. This means that we still have the original entity data array. I simply added a sourceEntityData argument to loadOneToOneEntity and provided this initial data. If the non-owning side cannot find data in the entity then it uses the original data array (which contains a superset of the entity data at this point) to form the query. This is a straightforward fix that solves the problem.

@greg0ire
Copy link
Member

greg0ire commented Dec 8, 2023

assuming BC means Backwards Compatibility, which is NO

What else could it be confused with? 🤔

@mcurland
Copy link
Contributor Author

mcurland commented Dec 9, 2023

assuming BC means Backwards Compatibility, which is NO

What else could it be confused with? 🤔

When I'm on the first line of a bug report the last thing I want to do is think about what the template means. I was thought out and running on less than 4 hours sleep for the 3rd night running (sick kids and sick dad). So I lazily Googled "what does BC Break mean?" and got a hit back in a doctrine bug report from the same template that said "I don't know what this means". I immediately felt a strong kinship with the previous filer, even if Google (complete with AI feedback) didn't know either. If it says: Does this break backwards compatibility? then it would be even easier to decipher. Anyway, back to real issues with _B_roken _C_ode.

mcurland added a commit to mcurland/doctrine2 that referenced this issue Aug 17, 2024
If the source entity for an inverse (non-owning) 1-1 relationship is
identified by an association then the identifying association may not
be set when an inverse one-to-one association is resolved. This means
that no data is available in the entity to resolve the needed column
value for the join query.

The original entity data can be retrieved from the unit of work and
is used as a fallback to populate the query condition.

Fixes doctrine#11108
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 a pull request may close this issue.

2 participants