2012-01-31 2 views
17

In primo luogo, questa domanda è simile a How to re-save the entity as another row in Doctrine 2Symfony2/Doctrine: come ri-salvare un ente con un OneToMany come cascata nuova riga

La differenza è che io sto cercando di salvare i dati all'interno di un'entità che ha una relazione OneToMany. Vorrei ri-salvare l'entità come una nuova riga nell'entità padre (sul lato "uno") e poi come nuove righe in ogni figlio successivo (sul lato "molti").

Ho usato un esempio piuttosto semplice di una classe con molti studenti per semplificare la procedura.

Così potrei avere ClassroomA con id = 1 e ha 5 pupille (id da 1 a 5). Mi piacerebbe sapere come potrei, in Doctrine2, prendere quell'entità e salvarla nuovamente nel database (dopo i potenziali cambiamenti di dati) con tutti i nuovi ID e le righe originali non modificate durante il persist/flush.

Consente innanzitutto di definire le nostre entità di Doctrine.

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

} 

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

E la nostra generica funzione Azione:

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'); 
} 

Ho provato con il clone, ma che ha salvato solo il relazione genitore (Classroom nel nostro esempio) con un nuovo ID, mentre i dati dei bambini (Pupille) sono stati aggiornati ag prima degli ID originali.

Grazie in anticipo a qualsiasi assistenza.

risposta

30

La cosa con clone è ...

Quando un oggetto viene clonato, PHP 5 si esibirà una copia di tutte le proprietà dell'oggetto. Tutte le proprietà che sono riferimenti ad altre variabili, rimarranno riferimenti.

Se si utilizza Dottrina> = 2.0.2, è possibile implementare il proprio __clone personalizzato() metodo:

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); 
    } 
} 

NOTA: prima di Dottrina 2.0.2 non è possibile implementare un metodo __clone() nella vostra entità come la classe proxy generata implementa il proprio __clone() che non verifica o chiama parent::__clone(). Quindi dovrai fare un metodo separato per quello come clonePupils() (in Classroom) e chiamarlo dopo aver clonato l'entità. In entrambi i casi, è possibile utilizzare lo stesso codice all'interno dei metodi __clone() o clonePupils().

Quando si clona la classe genitore, questa funzione creerà anche una nuova raccolta completa di cloni oggetto figlio.

$cloneClassroom = clone $classroom; 
$cloneClassroom->clonePupils(); 

$em->persist($cloneClassroom); 
$em->flush(); 

Probabilmente si vorrà a cascata persistere sulla vostra collezione $pupils per rendere persistenti più facile, ad esempio,

/** 
* @ORM\OneToMany(targetEntity="Pupil", mappedBy="classroom", cascade={"persist"}) 
*/ 
protected $pupils; 
+4

Che ha fatto, grazie. Speravo di evitare di dover eseguire il ciclo in una funzione, ma questa era la soluzione più semplice alla fine. Ho visto che la classe EntityManager di Doctrine ha una funzione di "copia" descritta come "Crea una copia dell'entità data. Può creare una copia superficiale o profonda". L'unico problema è che la funzione ha una riga che genera un'eccezione "Non implementata". – dividebyzeroZA

+2

FYI in corrente Doctrine2 è possibile implementare il proprio '__clone' e verrà chiamato. – jcbwlkr

-3

faccio questo:

if ($form->isValid()) { 
    foreach($classroom->getPupils() as $pupil) { 
     $pupil->setClassroom($classroom); 
    } 
    $em->persist($classroom); 
    $em->flush(); 
} 
+3

Questo non sembra creerà nuove istanze di Pupilla; condividerà ogni allievo tra le due classi. –

2

ho fatto come questo e si funziona bene.

All'interno dell'entità clonata abbiamo magic __clone(). Inoltre, non dimentichiamo il nostro uno-a-molti.

/** 
* 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; 
} 

Da qualche parte appena clonarlo:

// 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();