Symfony2/Doctrine: How to re-save an entity with a OneToMany as a cascading new row
clone, doctrine, doctrine-orm, php, symfony
Solution
The thing with `clone` is...
When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. Any properties that are references to other variables, will remain references.
If you are using Doctrine >= 2.0.2, you can implement your own custom __clone() method:
public function __clone() {
// Get current collection
$pupils = $this->getPupils();
$this->pupils = new ArrayCollection();
foreach ($pupils as $pupil) {
$clonePupil = clone $pupil;
$this->pupils->add($clonePupil);
$clonePupil->setClassroom($this);
}
}
NOTE: before Doctrine 2.0.2 you cannot implement a `__clone()` method in your entity as the generated proxy class implements its own `__clone()` which does not check for or call `parent::__clone()`. So you'll have to make a separate method for that like `clonePupils()` (in `Classroom`) instead and call that after you clone the entity. Either way, you can use the same code inside your `__clone()` or `clonePupils()` methods.
When you clone your parent class, this function will create a new collection full of child object clones as well.
$cloneClassroom = clone $classroom;
$cloneClassroom->clonePupils();
$em->persist($cloneClassroom);
$em->flush();
You'll probably want to cascade persist on your `$pupils` collection to make persisting easier, eg
/**
* @ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom", cascade={"persist"})
*/
protected $pupils;
Problem
Firstly, this question is similar to How to re-save the entity as another row in Doctrine 2 The difference is that I'm trying to save the data within an entity that has a OneToMany relationship. I'd like to re-save the entity as a new row in the parent entity (on the "one" side) and then as new rows in each subsequent child (on the "many" side). I've used a pretty simple example of a Classroom having many Pupils to keep it simple. So me might have ClassroomA with id=1 and it has 5 pupils (ids 1 through 5). I'd like to know how I could, within Doctrine2, take that Entity and re-save it to the database (after potential data changes) all with new IDs throughout and the original rows being untouched during the persist/flush. Lets first define our Doctrine Entities. The Classroom Entity: ``` namespace Acme\TestBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * @ORM\Entity * @ORM\Table(name="classroom") */ class Classroom { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $miscVars; /** * @ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom") */ protected $pupils; public function __construct() { $this->pupils = new ArrayCollection(); } // ========== GENERATED GETTER/SETTER FUNCTIONS BELOW ============ } ``` The Pupil Entity: ``` namespace Acme\TestBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * @ORM\Entity * @ORM\Table(name="pupil") */ class Pupil { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $moreVars; /** * @ORM\ManyToOne(targetEntity="Classroom", inversedBy="pupils") * @ORM\JoinColumn(name="classroom_id", referencedColumnName="id") */ protected $classroom; // ========== GENERATED FUNCTIONS BELOW ============ } ``` And our generic Action function: ``` public function someAction(Request $request, $id) { $em = $this->getDoctrine()->getEntityManager(); $classroom = $em->find('AcmeTestBundle:Classroom', $id); $form = $this->createForm(new ClassroomType(), $classroom); if ('POST' === $request->getMethod()) { $form->bindRequest($request); if ($form->isValid()) { // Normally you would do the following: $em->persist($classroom); $em->flush(); // But how do I create a new row with a new ID // Including new rows for the Many side of the relationship // ... other code goes here. } } return $this->render('AcmeTestBundle:Default:index.html.twig'); } ``` I've tried using clone but that only saved the parent relationship (Classroom in our example) with a fresh ID, while the children data (Pupils) was updated against the original IDs. Thanks in advance to any assistance.