Deep-Copy auf ein Doctrine Entity

Um ein Doctrine-Entity zu kopieren und neu zu speichern, sodaß man 2 Zeilen mit den gleichen Daten aber neuer ID in der Datenbank-Tabelle hat, genügt im Normalfall ein einfaches clone auf das Entity. Hat man im Entity aber Beziehungen zu anderen Entities gespeichert, werden diese nicht mitkopiert. Dazu muß man die „magische Funktion“ __clone im Entity erweitern:

Als Beispiel folgende Entities:

class Basket {
    
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     */
    protected $id;
    
    /**
     * @ORM\OneToMany(targetEntity="BasketEntry", mappedBy="basket", fetch="LAZY")
     */    
    protected $entries;

    // hier weitere properties und getter/setter Methoden
}

class BasketEntry {
    
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     */
    private $id;
    
    /**
     * @ORM\ManyToOne(targetEntity="Basket", inversedBy="entries")
     * @JoinColumn(name="basket_id", referencedColumnName="id")
     */
    private $basket;

    // hier weitere properties und getter/setter Methoden
}       

Wollen wir nun den Warenkorb nocheinmal abspeichern, würde mit einem einfachen clone zwar eine Kopie des Warenkorbs entstehen, aber nicht von den einzelnen Einträgen. Wir müssen deshalb die Basket-Entity um eine __clone-Funktion erweitern und dort die Warenkorb-Positionen einzeln kopieren:

class Basket {
    
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     */
    protected $id;
    
    /**
     * @ORM\OneToMany(targetEntity="BasketEntry", mappedBy="basket", fetch="LAZY")
     */    
    protected $entries;

    public function __clone() {
        // id auf null setzen, damit ein neuer Eintrag entsteht
        $this->setId(null);
        // die Warenkorb-Einträge einzeln klonen
        $entries = [];
        foreach ($this->getEntries() as $entry) {
            $newEntry = clone $entry;
            $newEntry->setId(null);
            $newEntry->setBasket($this);
            $entries[] = $newEntry;
        }
        $this->setEntries($entries);
    }

}

Um nun einen Warenkorb zu kopieren und neu zu speichern, müssen wir z.B. im Entity-Repository von Basket folgendes durchführen:

$basketCopy = clone($basket);
$entityManager->persist($basketCopy);
$entityManager->flush();

Ein Hinweis: Doctrine´s EntityManager hat zwar eine Methode copy, welche laut Dokumentation ein DeepCopy können sollte, aber diese Methode ist (noch) nicht implementiert:

http://www.doctrine-project.org/api/orm/2.5/source-class-Doctrine.ORM.EntityManager.html#676-685