2012-11-17 4 views
7

Customer è il lato inverso del rapporto "parole chiave/clienti" con Keyword:Aggiornamento (dal lato opposto) delle relazioni bidirezionali bidirezionali in Doctrine 2?

/** 
* @ORM\ManyToMany(targetEntity="Keyword", mappedBy="customers", 
*  cascade={"persist", "remove"} 
*) 
*/ 
protected $keywords; 

Quando si crea un nuovo cliente, si dovrebbe selezionare una o più parole chiave. Il campo modulo entità è:

$form->add($this->factory->createNamed('entity', 'keywords', null, array(
    'class' => 'Acme\HelloBundle\Entity\Keyword', 
    'property' => 'select_label', 
    'multiple' => true, 
    'expanded' => true, 
))); 

Nel mio codice di controllo, dopo vincolante la richiesta e verificare se la forma è valida, ho bisogno di persistere sia il cliente e tutte le associazioni cliente/parola chiave (s), che è il join tavolo.

Tuttavia cliente è il lato inverso, quindi questo non funziona:

if($request->isPost()) { 
    $form->bindRequest($request); 

    if(!$form->isValid()) { 
     return array('form' => $form->createView()); 
    } 

    // Valid form here 
    $em = $this->getEntityManager(); 

    $em->persist($customer);  
    $em->flush(); 
} 

evento con l'opzione "a cascata", questo codice non riesce. $customer->getKeywords() restituirà Doctrine\ORM\PersistentCollection, che contiene solo parole chiave selezionate.

Devo controllare manualmente quale parola chiave è stata rimossa/aggiunta e quindi aggiornata dal lato proprietario?

+0

Ho pubblicato la mia soluzione [qui] [1]. Spero che ti sarà d'aiuto. [1]: http://stackoverflow.com/a/27113108/3133441 – ajtamwojtek

risposta

10

Ok, ho trovato la via, anche se non sono completamente soddisfatto. La chiave era this example tipo di campo di raccolta modulo. In sostanza quello che sta succedendo con il mio precedente definizione del modulo è stato:

$customer->getKeywords() = $postData; // $postData is somewhere in form framework 

E questo è solo un incarico di una raccolta (di parole chiave selezionate) per le parole chiave dei clienti. Nessun metodo è stato invocato sulle istanze Keyword (lato proprietario). L'opzione chiave è by_reference (per me è solo un brutto nome, ma comunque ...):

$form 
    ->add($this->factory->createNamed('entity', 'keywords', null, array(
     // ... 
     'by_reference' => false 
    )) 
); 

In questo modo il framework dei form sta per chiamare il setter, che è $customer->setKeywords(Collection $keywords). In questo metodo, si può "raccontare" il lato possessore di memorizzare la vostra associazione:

public function setKeywords(Collection $keywords) 
{ 
    foreach($keywords as $keyword) { 
     $keyword->addCustomer($this); // Owning side call! 
    } 

    $this->keywords = $keywords; 

    return $this; 
} 

(Controllare sempre per le istanze duplicate sul lato possessore, usando contains metodo).

A questo punto, verranno aggiunte solo le parole chiave verificate (argomento $keyword). C'è la necessità di gestire la rimozione delle parole chiave non controllate (lato controller):

$originalKeywords = $customer->getKeywords()->toArray(); // When GET or POST 

// When POST and form valid 
$checkedKeywords = $customer->getKeywords()->toArray(); // Thanks to setKeywords 

// Loop over all keywords 
foreach($originalKeywords as $keyword) { 
    if(!in_array($keyword, $checkedKeywords)) { // Keyword has been unchecked 
     $keyword->removeCustomer($customer); 
     $manager->persist($keyword); 
    } 
} 

Brutto, ma funziona. Avrei il codice per la rimozione spostato nella classe Customer, ma non è affatto possibile. Se trovi una soluzione migliore, fammi sapere!

2

Io uso una soluzione leggermente diversa da @gredmo. Da doctrine documentation: è possibile utilizzare rimozione Orphan quando si soddisfano questo presupposto:

Quando si utilizza l'opzione Dottrina orphanRemoval=true parte dal presupposto che le entità sono di proprietà privata e sarà NON essere riutilizzati da altre entità. Se trascuri questa ipotesi, le tue entità verranno eliminate da Doctrine anche se hai assegnato l'entità orfana a un'altra.

ho questa classe di entità:

class Contract { 
/** 
* @ORM\OneToMany(targetEntity="ContractParticipant", mappedBy="contract", cascade={"all"}, orphanRemoval=true) 
**/ 
} 
protected $participants; 

modulo di elaborazione (pseudo codice):

// $_POST carry the Contract entity values 

    $received = []; 

    foreach ($_POST['participants'] as $key => $participant) { 

     if ((!$relation = $collection->get($key))) { 
      // new entity 
      $collection[$key] = $relation = $relationMeta->newInstance(); 

     } else { 
      // editing existing entity 
     } 

     $received[] = $key; 
     $this->mapper->save($relation, $participant); // map POST data to entity 
    } 

    foreach ($collection as $key => $relation) { 
     if ($this->isAllowedRemove() && !in_array($key, $received)) { 
      // entity has been deleted 
      unset($collection[$key]); 
     } 
    } 

Non dimenticare di persistere a filo fine entità. Flush elimina anche le entità rimosse.

$this->em->persist($entity); 
    $this->em->flush();