orm icon indicating copy to clipboard operation
orm copied to clipboard

Incorrect / lost cloning of an associated entity with LazyGhosts

Open jonnyeom opened this issue 11 months ago • 1 comments

Bug Report

Q A
Version 2.19.3 / 3.x

Summary

When you clone an entity with an associated entity that is loaded as a Lazy Ghost Proxy, in certain circumstances, you lose the previous cloning functionality.

In most cases, with a clone, it is just a reference to the associated entity.

However, in oneToOne relationships, orm cannot keep the reference. You would have to duplicate the entity properties.

Preivous With enable_lazy_ghost_objects: false, we used to have \Doctrine\ORM\Proxy\ProxyFactory::createCloner() to finalize proxy entities when cloned. This was deprecated. I would assume its functionality should be preserved.

Current With enable_lazy_ghost_objects: true, we rely on \Doctrine\ORM\Proxy\ProxyFactory::getProxyFactory(). This has an initializer that will initialize identifiers correctly. However all other data is lost.

Current behavior

Lets say we have a Parent $parent entity with a Child $child entity with a OneToOne relationship.
And Child has a required property $name. and a auto generated ID property $id.

    class Parent {

        #[ORM\OneToOne(targetEntity: Child::class, cascade: ['persist'], inversedBy: 'parent')]
        private ?Child $child = null;
    }

    class Child {

        #[ORM\OneToOne(mappedBy: 'child', targetEntity: Parent::class)]
        private Parent $parent;
        
        #[ORM\Column(length: 255, nullable: false)]
        private string $name;

        ....
    }
    $parent->getChild(); // Proxy of $child. Empty. 
    $clonedParent = clone $parent;

    $clonedParent->getChild(); // Proxy of $child. Still Empty.

    $entityManager->persist($clonedParent);
    $entityManager->flush() // Error! $name is null.

Expected behavior

    $parent->getChild(); // Proxy of $child. Empty. 
    $clonedParent = clone $parent;

    $clonedParent->getChild(); // Proxy of $child. Still Empty.

    $entityManager->persist($clonedParent);
    $entityManager->flush() // New $clonedParent persisted. New $child persisted with different ID and same properties. (Same as before).

Current workaround

You can workaround this by forcing the Proxied entity to fully initialize before cloning

    $parent->getChild(); // Proxy of $child. Empty. 
    $parent->getChild()->getName(); // Force orm to load Proxy.
    $parent->getChild(); // Now a fully loaded Proxy;

    $clonedParent = clone $parent;
    $clonedParent->getChild(); // Proxy of $child. Fully loaded.

    $entityManager->persist($clonedParent);
    $entityManager->flush() // Persisted as expected

How to reproduce

Reproducer > https://github.com/jonnyeom/clone-lazy-ghost-proxy-reproducer

Possible solution

I honestly think all we need is to restore the __clone() on the Proxy entity.

jonnyeom avatar Jan 08 '25 18:01 jonnyeom

Here is the reproducer > https://github.com/jonnyeom/clone-lazy-ghost-proxy-reproducer

jonnyeom avatar Jan 09 '25 15:01 jonnyeom

This also happens in doctrine/mongodb-odm and data is lost completely on cloning.

Nilz11 avatar Oct 09 '25 13:10 Nilz11

@jonnyeom Could you find a better workaround?

Nilz11 avatar Oct 09 '25 13:10 Nilz11

@beberlei is cloning entities a valid and supported use case in the first place?

mpdude avatar Oct 10 '25 05:10 mpdude

@jonnyeom Could you find a better workaround?

No I havent! I just have an extra line of code to ensure initialization. and commented this issue for now. Its a little tacky but its working for us!

jonnyeom avatar Oct 13 '25 14:10 jonnyeom