54

I have created an entity A with OneToMany relation to B, which have relation OneToMany to C.

I have to clone this A entity and set it in database with new id. Also all deep relations should be cloned with new ids too.

What have I tried is to set A id to null:

$A = clone $A_original;
$A->setId(null);
$em->persist($A);

It creates new record in A table, but does not in B and C.

What should I do to make a full copy of A entity ?

hsz
  • 148,279
  • 62
  • 259
  • 315

4 Answers4

88

You have to implement a __clone() method in your entities that sets the id to null and clones the relations if desired. Because if you keep the id in the related object it assumes that your new entity A has a relation to the existing entities B and C.

Clone-method for A:

public function __clone() {
    if ($this->id) {
        $this->setId(null);
        $this->B = clone $this->B;
        $this->C = clone $this->C;
    }
}

Clone-method for B and C:

public function __clone() {
    if ($this->id) {
        $this->setId(null);
    }
}

https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/Nu2rayrDkgQ

https://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html

Based on the comment of coder4show a clone-method for a OneToMany relationship on A where $this->M is OneToMany and therefore an ArrayCollection:

public function __clone() {
    if ($this->id) {
        $this->setId(null);

        // cloning the relation M which is a OneToMany
        $mClone = new ArrayCollection();
        foreach ($this->M as $item) {
            $itemClone = clone $item;
            $itemClone->setA($this);
            $mClone->add($itemClone);
        }
        $this->M = $mClone;
    }
}
flec
  • 2,891
  • 1
  • 22
  • 30
  • The only problem with this is, it doesn't seem to work on OneToMany, any ideas why? – codeguy Oct 22 '15 at 09:15
  • 1
    @coder4show that is because you are cloning the `ArrayCollection` and not the elements in it. I have added a clone method for a OneToMany relationship. – flec Oct 23 '15 at 11:17
  • 1
    In this example the OneToMany relationship will not be cloned correctly, as the cloned related entities still refer to the old entity. See [this answer](http://stackoverflow.com/a/28313673/3215645) for a working example – Richard Nov 12 '15 at 09:54
  • 1
    @Richard thank you, you are right. I have updated the example to make it work. – flec Nov 15 '15 at 10:05
  • 3
    Is there a particular reason to use `$this->setId(null)` rather than `$this->id = null` ? – Pierre de LESPINAY Aug 29 '16 at 14:24
  • 2
    @PierredeLESPINAY If you have a setter I would recommend using it because there might be some other logic in it that should be triggered when changing the id. Other than that I see no particular reason to use the setter. For cloning it should have the same effect. – flec Sep 04 '16 at 11:01
  • 1
    Yes this is the aim of accessors, but we don't often see them used from the owner entities (`$this`). For example you are setting `$this->M` directly here. – Pierre de LESPINAY Sep 05 '16 at 07:50
  • 1
    Another synthax to clone a collection `$this->M = $this->M->map(function ($item) { return (clone $item)->setA($this);} ` – GuillaumeL Apr 17 '19 at 15:06
9

There is also a module that will do this called DeepCopy:

https://github.com/myclabs/DeepCopy

$deepCopy = new DeepCopy();
$myCopy   = $deepCopy->copy($myObject);

You can also add filters to customize the copy process.

Onshop
  • 2,915
  • 1
  • 27
  • 17
  • Hi @Ben can you take a look to [this](http://stackoverflow.com/questions/28650001/clone-entity-in-cascade-mode?noredirect=1#comment45641837_28650001) – ReynierPM Feb 26 '15 at 01:28
  • 3
    I have found this incredibly buggy and its faster to just roll your own `clone` methods – Jake N Nov 25 '15 at 14:17
3

I wasnt able to use DeepClone (it require php 7.1+), so I founded more simple way to clone relations in entity __clone method

$this->tags = new ArrayCollection($this->tags->toArray());
Andrew Zhilin
  • 1,654
  • 16
  • 11
  • 1
    Perfect ! It works. I'm using iterable so it looks like : `$this->foos = \iterable_to_array($this->foos);` – Athos Jul 06 '20 at 14:58
0

A clean way to clone a ArrayCollection:

$this->setItems(
    $this->getItems()->map(function (Item $item) {
        return clone $item;
    })
);

DMat
  • 136
  • 1
  • 5