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

Open
mcurland opened this issue Dec 6, 2023 · 2 comments · May be fixed by #11109
Open

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 · May be fixed by #11109

Comments

@mcurland
Copy link

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.

@mcurland mcurland linked a pull request Dec 6, 2023 that will close this issue
@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
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.

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