Incorrect / lost cloning of an associated entity with LazyGhosts #7460

Open
opened 2026-01-22 15:51:56 +01:00 by admin · 5 comments
Owner

Originally created by @jonnyeom on GitHub (Jan 8, 2025).

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.

Originally created by @jonnyeom on GitHub (Jan 8, 2025). ### Bug Report <!-- Fill in the relevant information below to help triage your issue. --> | 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. ```php 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; .... } ``` ```php $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 ```php $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 ```php $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.
Author
Owner

@jonnyeom commented on GitHub (Jan 9, 2025):

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

@jonnyeom commented on GitHub (Jan 9, 2025): Here is the reproducer > https://github.com/jonnyeom/clone-lazy-ghost-proxy-reproducer
Author
Owner

@Nilz11 commented on GitHub (Oct 9, 2025):

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

@Nilz11 commented on GitHub (Oct 9, 2025): This also happens in doctrine/mongodb-odm and data is lost completely on cloning.
Author
Owner

@Nilz11 commented on GitHub (Oct 9, 2025):

@jonnyeom Could you find a better workaround?

@Nilz11 commented on GitHub (Oct 9, 2025): @jonnyeom Could you find a better workaround?
Author
Owner

@mpdude commented on GitHub (Oct 10, 2025):

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

@mpdude commented on GitHub (Oct 10, 2025): @beberlei is cloning entities a valid and supported use case in the first place?
Author
Owner

@jonnyeom commented on GitHub (Oct 13, 2025):

@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 commented on GitHub (Oct 13, 2025): > [@jonnyeom](https://github.com/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!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7460