Symfony2 / Doctrine: how to re-save an object with OneToMany as a cascading new line

Firstly, this question is similar to How to re-save an object as another row in Doctrine 2

The difference is that I'm trying to save data in an entity that has OneToMany relationships. I would like to re-save the object as a new line in the parent object (on the "one" side), and then as new lines in each subsequent child element (on the "many" side).

I used a fairly simple example of a class in which there were many students to make it simple.

So, I can have ClassroomA with id = 1 and it has 5 students (from 1 to 5). I would like to know how, in Doctrine2, I could take this Entity and save it in the database again (after potential data changes), all with new identifiers everywhere, and the original lines remain untouched during persist / flush.

Lets you first define our Doctrine objects.

Class object:

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 ============ } 

Student Institution:

 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 common 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 tried using a clone, but only kept the parent relationship (class in our example) with a fresh identifier, while the data about children (students) were updated compared to the original identifiers.

Thanks in advance for any help.

+17
clone php symfony doctrine doctrine2
Jan 31 '12 at 23:26
source share
3 answers

The thing with clone is ...

When an object is cloned, PHP 5 makes a shallow copy of all properties of the object. Any properties that are references to other variables will remain references.

If you use Doctrine> = 2.0.2, you can implement your own __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. Prior to Doctrine 2.0.2, you cannot implement the __clone() method in your essence, since the generated proxy class implements its own __clone() , which does not check or call parent::__clone() . So you have to make a separate method for this, for example clonePupils() (in Classroom ), and call it after you clone the object. In any case, you can use the same code inside the __clone() or clonePupils() methods.

When you clone a parent class, this function will create a new collection full of clones of child objects.

 $cloneClassroom = clone $classroom; $cloneClassroom->clonePupils(); $em->persist($cloneClassroom); $em->flush(); 

You probably want to keep the cascade in your $pupils collection to make $pupils easier, like

 /** * @ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom", cascade={"persist"}) */ protected $pupils; 
+30
01 Feb '12 at 1:18
source share

I did it like this and it works great.

Inside the cloned entity, we have the magic of __clone () . There we also do not forget our one-to-many .

 /** * Clone element with values */ public function __clone(){ // we gonna clone existing element if($this->id){ // get values (one-to-many) /** @var \Doctrine\Common\Collections\Collection $values */ $values = $this->getElementValues(); // reset id $this->id = null; // reset values $this->elementValues = new \Doctrine\Common\Collections\ArrayCollection(); // if we had values if(!$values->isEmpty()){ foreach ($values as $value) { // clone it $clonedValue = clone $value; // add to collection $this->addElementValues($clonedValue); } } } } /** * addElementValues * * @param \YourBundle\Entity\ElementValue $elementValue * @return Element */ public function addElementValues(\YourBundle\Entity\ElementValue $elementValue) { if (!$this->getElementValues()->contains($elementValue)) { $this->elementValues[] = $elementValue; $elementValue->setElement($this); } return $this; } 

Somewhere just clone it:

 // Returns \YourBundle\Entity\Element which we wants to clone $clonedEntity = clone $this->getElement(); // Do this to say doctrine that we have new object $this->em->persist($clonedEntity); // flush it to base $this->em->flush(); 
+2
Sep 30 '14 at 11:12
source share

I'm doing it:

 if ($form->isValid()) { foreach($classroom->getPupils() as $pupil) { $pupil->setClassroom($classroom); } $em->persist($classroom); $em->flush(); } 
-3
Apr 05 '12 at 19:25
source share



All Articles